using System.Text.Json;
using ClawdDotNet.Core.Api;
using ClawdDotNet.Core.Api.Models;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Api;

/// <summary>
/// B4, zweiter Teil: Die Preise standen fest im Code, waren veraltet, und ausgerechnet
/// das Standardmodell der Agenten fehlte. CalculateCost gab dafür stillschweigend 0
/// zurück — die Anzeige meldete also 0 €, während echte Kosten anfielen.
/// </summary>
public sealed class ModelPricingTests
{
    // ═══════════════════════════════════════════════════════════
    //  Umrechnung der OpenRouter-Angaben
    // ═══════════════════════════════════════════════════════════

    [Theory]
    [InlineData("0.000003", 3.0)]        // 3 $ pro 1M Token
    [InlineData("0.000015", 15.0)]
    [InlineData("0.0000001", 0.1)]
    [InlineData("0", 0.0)]
    public void Preise_pro_Token_werden_auf_eine_Million_hochgerechnet(string raw, double expected)
    {
        ModelPricing.ParsePerToken(raw).ShouldBe((decimal)expected);
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("keine-zahl")]
    [InlineData("-0.001")]
    public void Unbrauchbare_Angaben_ergeben_null(string? raw)
    {
        ModelPricing.ParsePerToken(raw).ShouldBeNull();
    }

    [Fact]
    public void Die_Umrechnung_ist_unabhaengig_von_der_Systemsprache()
    {
        // Mit deutscher Kultur würde "0.000003" sonst als 3 statt als 0,000003 gelesen.
        var original = Thread.CurrentThread.CurrentCulture;
        try
        {
            Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE");
            ModelPricing.ParsePerToken("0.000003").ShouldBe(3.0m);
        }
        finally
        {
            Thread.CurrentThread.CurrentCulture = original;
        }
    }

    // ═══════════════════════════════════════════════════════════
    //  Auslesen aus der API-Antwort
    // ═══════════════════════════════════════════════════════════

    [Fact]
    public void Preise_werden_aus_der_Modellantwort_gelesen()
    {
        var item = JsonDocument.Parse("""
            { "id": "anthropic/claude-sonnet-4-5",
              "pricing": { "prompt": "0.000003", "completion": "0.000015" } }
            """).RootElement;

        var pricing = OpenRouterClient.ReadPricing(item);

        pricing.ShouldNotBeNull();
        pricing.InputPer1M.ShouldBe(3.0m);
        pricing.OutputPer1M.ShouldBe(15.0m);
    }

    [Theory]
    [InlineData("""{ "id": "x" }""")]
    [InlineData("""{ "id": "x", "pricing": {} }""")]
    [InlineData("""{ "id": "x", "pricing": { "prompt": "0.000003" } }""")]
    [InlineData("""{ "id": "x", "pricing": "kostenlos" }""")]
    public void Unvollstaendige_Preisangaben_ergeben_null(string json)
    {
        // Halbe Angaben sind schlimmer als keine — sie ergäben eine plausibel
        // aussehende, aber falsche Summe.
        OpenRouterClient.ReadPricing(JsonDocument.Parse(json).RootElement).ShouldBeNull();
    }

    // ═══════════════════════════════════════════════════════════
    //  Katalog und Kostenschätzung
    // ═══════════════════════════════════════════════════════════

    private static ModelPricingCatalog CatalogWith(params (string Id, decimal In, decimal Out)[] models)
    {
        var catalog = new ModelPricingCatalog();
        catalog.Load(models.Select(m => new ModelInfo
        {
            Id = m.Id,
            Name = m.Id,
            Pricing = new ModelPricing(m.In, m.Out)
        }));
        return catalog;
    }

    [Fact]
    public void Die_Kosten_werden_aus_Prompt_und_Completion_getrennt_berechnet()
    {
        var catalog = CatalogWith(("anthropic/claude-sonnet-4-5", 3m, 15m));

        // 95.000 Eingabe + 5.000 Ausgabe = 0,285 + 0,075 = 0,36 $
        var estimate = catalog.Estimate("anthropic/claude-sonnet-4-5", 95_000, 5_000);

        estimate.IsKnown.ShouldBeTrue();
        estimate.Usd.ShouldBe(0.36m);
    }

    [Fact]
    public void Ein_unbekanntes_Modell_wird_als_unbekannt_gemeldet_statt_als_null_Kosten()
    {
        // Der Kern des alten Fehlers: 0 sah aus wie kostenlos.
        var catalog = CatalogWith(("openai/gpt-4o", 2.5m, 10m));

        var estimate = catalog.Estimate("anthropic/claude-sonnet-4-5", 100_000, 5_000);

        estimate.IsKnown.ShouldBeFalse();
        estimate.Model.ShouldBe("anthropic/claude-sonnet-4-5");
    }

    [Fact]
    public void Grossschreibung_der_Modell_ID_spielt_keine_Rolle()
    {
        var catalog = CatalogWith(("openai/gpt-4o", 2.5m, 10m));

        catalog.IsKnown("OpenAI/GPT-4o").ShouldBeTrue();
    }

    [Fact]
    public void Modelle_ohne_Preisangabe_landen_nicht_im_Katalog()
    {
        var catalog = new ModelPricingCatalog();
        catalog.Load([
            new ModelInfo { Id = "mit-preis", Pricing = new ModelPricing(1m, 2m) },
            new ModelInfo { Id = "ohne-preis", Pricing = null }
        ]);

        catalog.IsKnown("mit-preis").ShouldBeTrue();
        catalog.IsKnown("ohne-preis").ShouldBeFalse();
        catalog.Count.ShouldBe(1);
    }

    [Fact]
    public void Ein_leerer_Katalog_kennt_kein_Modell()
    {
        new ModelPricingCatalog().Estimate("beliebig", 1000, 100).IsKnown.ShouldBeFalse();
    }

    [Fact]
    public void Erneutes_Laden_aktualisiert_die_Preise()
    {
        var catalog = CatalogWith(("m", 1m, 2m));
        catalog.Load([new ModelInfo { Id = "m", Pricing = new ModelPricing(5m, 10m) }]);

        catalog.Get("m")!.InputPer1M.ShouldBe(5m);
    }
}
