using ClawdDotNet.Core.Api.Models;
using ClawdDotNet.Core.Config;
using ClawdDotNet.Core.Engine;
using ClawdDotNet.Core.Tests.Infrastructure;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Engine;

public sealed class TokenAccountingTests
{
    // ═══════════════════════════════════════════════════════════
    //  B4 — Prompt und Completion getrennt erfassen
    // ═══════════════════════════════════════════════════════════

    [Fact]
    public async Task Prompt_und_Completion_werden_getrennt_aufsummiert()
    {
        // Bisher schätzte die UI 50/50. Real liegt das Verhältnis eher bei 95:5 —
        // und da Ausgabe-Tokens ein Vielfaches kosten, war die Kostenanzeige
        // um ein Mehrfaches daneben.
        var fixture = new EngineFixture().WithTool(FakeTool.Returning("ok"));
        var agent = fixture.AddAgent("agent-tokens", "TestTool");

        fixture.Client
            .RespondsWithText("Fertig", new Usage
            {
                PromptTokens = 9_500,
                CompletionTokens = 500,
                TotalTokens = 10_000
            });

        var result = await fixture.Engine.ChatAsync(agent, "Frage", "test-instance", default);

        result.PromptTokens.ShouldBe(9_500);
        result.CompletionTokens.ShouldBe(500);
        result.TokensUsed.ShouldBe(10_000);
    }

    [Fact]
    public async Task Ueber_mehrere_Schritte_wird_korrekt_summiert()
    {
        var fixture = new EngineFixture().WithTool(FakeTool.Returning("ok"));
        var agent = fixture.AddAgent("agent-sum", "TestTool");

        fixture.Client
            .RespondsWithToolCall("TestTool")        // 100 / 20 / 120 laut Fake
            .RespondsWithToolCall("TestTool")
            .RespondsWithText("Fertig");

        var result = await fixture.Engine.ChatAsync(agent, "Frage", "test-instance", default);

        result.StepCount.ShouldBe(3);
        result.PromptTokens.ShouldBe(300);
        result.CompletionTokens.ShouldBe(60);
        result.TokensUsed.ShouldBe(360);
    }

    // ═══════════════════════════════════════════════════════════
    //  T9 — Cache-Wirkung messbar machen
    // ═══════════════════════════════════════════════════════════

    [Fact]
    public async Task Gecachte_Tokens_werden_durchgereicht()
    {
        // Ohne diese Zahl liesse sich nicht belegen, ob das Prompt-Caching greift —
        // ein still wirkungsloser Cache wäre sonst nicht zu bemerken.
        var fixture = new EngineFixture();
        var agent = fixture.AddAgent("agent-cached");

        fixture.Client.RespondsWithText("Fertig", new Usage
        {
            PromptTokens = 20_000,
            CompletionTokens = 300,
            TotalTokens = 20_300,
            PromptTokensDetails = new PromptTokensDetails { CachedTokens = 18_000 }
        });

        var result = await fixture.Engine.ChatAsync(agent, "Frage", "test-instance", default);

        result.CachedTokens.ShouldBe(18_000);
    }

    [Fact]
    public void Fehlende_Cache_Angaben_ergeben_null_statt_eines_Fehlers()
    {
        var usage = new Usage { PromptTokens = 100, CompletionTokens = 10, TotalTokens = 110 };

        usage.CachedTokens.ShouldBe(0);
    }

    // ═══════════════════════════════════════════════════════════
    //  T3 — Compaction läuft mit dem günstigen Modell
    // ═══════════════════════════════════════════════════════════

    [Fact]
    public async Task Die_Zusammenfassung_nutzt_das_guenstige_Modell()
    {
        var client = new FakeChatClient().AlwaysRespondsWithText("- Zusammenfassung.");
        var compactor = new ContextCompactor(client, TestLogging.Factory);

        var guard = new LoopGuardConfig
        {
            MaxContextTokens = 1_000,
            CompactionThreshold = 0.5,
            SummaryModel = "google/gemini-2.5-flash"
        };

        var messages = Conversation.Start().Repeat(10).Build();

        await compactor.CompactIfNeededAsync(messages, 50_000, guard, "anthropic/claude-opus-4", default);

        client.ReceivedRequests.ShouldNotBeEmpty();
        client.ReceivedRequests[0].Model.ShouldBe("google/gemini-2.5-flash",
            "Zusammenfassen ist anspruchslos und darf nicht das teure Agentenmodell belegen");
    }

    [Fact]
    public async Task Ohne_konfiguriertes_SummaryModel_wird_das_Agentenmodell_verwendet()
    {
        var client = new FakeChatClient().AlwaysRespondsWithText("- Zusammenfassung.");
        var compactor = new ContextCompactor(client, TestLogging.Factory);

        var guard = new LoopGuardConfig
        {
            MaxContextTokens = 1_000,
            CompactionThreshold = 0.5,
            SummaryModel = ""
        };

        var messages = Conversation.Start().Repeat(10).Build();

        await compactor.CompactIfNeededAsync(messages, 50_000, guard, "anthropic/claude-opus-4", default);

        client.ReceivedRequests[0].Model.ShouldBe("anthropic/claude-opus-4");
    }

    // ═══════════════════════════════════════════════════════════
    //  Nur der wegfallende Teil wird zusammengefasst
    // ═══════════════════════════════════════════════════════════

    [Fact]
    public async Task Der_erhaltene_Tail_geht_nicht_in_den_Zusammenfassungs_Aufruf()
    {
        // Der Tail bleibt wörtlich erhalten — ihn zusätzlich zusammenzufassen
        // wäre doppelt bezahlter Kontext.
        var client = new FakeChatClient().AlwaysRespondsWithText("- Zusammenfassung.");
        var compactor = new ContextCompactor(client, TestLogging.Factory);

        var guard = new LoopGuardConfig { MaxContextTokens = 1_000, CompactionThreshold = 0.5 };

        var messages = Conversation.Start()
            .Repeat(8)
            .User("EINZIGARTIGE-LETZTE-NACHRICHT")
            .Build();

        await compactor.CompactIfNeededAsync(messages, 50_000, guard, "test/model", default);

        var summaryPrompt = client.ReceivedRequests[0].Messages.Last().Content!;
        summaryPrompt.ShouldNotContain("EINZIGARTIGE-LETZTE-NACHRICHT");
    }
}
