using ClawdDotNet.Core.Audit;
using ClawdDotNet.Core.Tests.Infrastructure;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Audit;

/// <summary>
/// A3 an der Engine: Jeder Tool-Aufruf wird protokolliert, jeder Lauf bekommt einen Beleg.
/// Entscheidend ist die Provenienz — die Engine stempelt Agent, Modell und Herkunft aus
/// ihrem eigenen Wissen, nicht aus dem Tool-Ergebnis.
/// </summary>
public sealed class EngineAuditTests
{
    [Fact]
    public async Task Jeder_Tool_Aufruf_landet_im_Audit_Log()
    {
        var audit = new InMemoryAuditRepository();
        var fixture = new EngineFixture(audit).WithTool(FakeTool.Returning("ok"));
        var agent = fixture.AddAgent("agent-a", "TestTool");

        // Zwei Tool-Aufrufe, dann Text.
        fixture.Client
            .RespondsWithToolCall("TestTool")
            .RespondsWithToolCall("TestTool")
            .RespondsWithText("fertig");

        await fixture.Engine.RunAsync(agent, "los", "test-instance", default);

        var entries = audit.Entries.ToList();
        entries.Count.ShouldBe(2);
        entries.ShouldAllBe(e => e.Tool == "TestTool");
        entries.ShouldAllBe(e => e.Status == AuditStatus.Ok);
    }

    [Fact]
    public async Task Die_Herkunft_wird_von_der_Engine_gestempelt()
    {
        var audit = new InMemoryAuditRepository();
        var fixture = new EngineFixture(audit).WithTool(FakeTool.Returning("ok"));
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

        await fixture.Engine.RunAsync(agent, "los", "test-instance", default, source: "task", taskId: "t-7");

        var entry = audit.Entries.Single();
        entry.AgentId.ShouldBe("agent-a");
        entry.Model.ShouldBe("test/model");
        entry.Source.ShouldBe("task");
        entry.RunId.ShouldNotBeNullOrWhiteSpace();
    }

    [Fact]
    public async Task Ein_verweigerter_Aufruf_wird_als_denied_protokolliert()
    {
        var audit = new InMemoryAuditRepository();
        // Tool ist registriert, aber dem Agenten NICHT zugewiesen → PermissionGate greift.
        var fixture = new EngineFixture(audit).WithTool(FakeTool.Returning("ok"));
        var agent = fixture.AddAgent("agent-a" /* kein Tool zugewiesen */);
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

        await fixture.Engine.RunAsync(agent, "los", "test-instance", default);

        audit.Entries.Single().Status.ShouldBe(AuditStatus.Denied);
    }

    [Fact]
    public async Task Ein_Tool_Fehler_wird_als_error_protokolliert()
    {
        var audit = new InMemoryAuditRepository();
        var fixture = new EngineFixture(audit)
            .WithTool(FakeTool.Throwing(new InvalidOperationException("kaputt")));
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

        await fixture.Engine.RunAsync(agent, "los", "test-instance", default);

        var entry = audit.Entries.Single();
        entry.Status.ShouldBe(AuditStatus.Error);
        entry.Summary.ShouldContain("kaputt");
    }

    [Fact]
    public async Task Ein_Lauf_bekommt_einen_Receipt_mit_Task_Verknuepfung()
    {
        var audit = new InMemoryAuditRepository();
        var fixture = new EngineFixture(audit).WithTool(FakeTool.Returning("ok"));
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("erledigt");

        await fixture.Engine.RunAsync(agent, "los", "test-instance", default, source: "task", taskId: "t-42");

        var receipt = audit.Receipts.Single();
        receipt.TaskId.ShouldBe("t-42");
        receipt.Source.ShouldBe("task");
        receipt.Status.ShouldBe("Completed");
        receipt.ResultRef.ShouldBe("erledigt");
        receipt.StepCount.ShouldBeGreaterThan(0);
    }

    [Fact]
    public async Task Receipt_und_Audit_teilen_dieselbe_RunId()
    {
        var audit = new InMemoryAuditRepository();
        var fixture = new EngineFixture(audit).WithTool(FakeTool.Returning("ok"));
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

        await fixture.Engine.RunAsync(agent, "los", "test-instance", default);

        var runId = audit.Receipts.Single().RunId;
        audit.Entries.Single().RunId.ShouldBe(runId, "alle Aufrufe eines Laufs tragen dieselbe RunId");
    }

    [Fact]
    public async Task Ohne_Audit_Repository_laeuft_alles_normal_weiter()
    {
        // Gegenprobe: A3 ist optional — die Engine darf ohne Audit-Repo nicht anders laufen.
        var fixture = new EngineFixture().WithTool(FakeTool.Returning("ok"));
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

        var result = await fixture.Engine.RunAsync(agent, "los", "test-instance", default);
        result.Status.ToString().ShouldBe("Completed");
    }
}
