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

namespace ClawdDotNet.Core.Tests.Staging;

/// <summary>
/// A2 an der Engine — die eigentliche Sicherheitswirkung: Eine <c>approve</c>-Aktion wird
/// <b>vorgeschlagen statt ausgeführt</b>. Eine Prompt-Injection kann damit nur einen
/// Vorschlag erzeugen, keine Ausführung.
/// </summary>
public sealed class EngineStagingTests
{
    private static StagingGate Gate(InMemoryStagingRepository repo, StagingDecision decision)
        => new(new StagingPolicy(new Dictionary<string, StagingDecision> { ["TestTool"] = decision }), repo);

    [Fact]
    public async Task Eine_freigabepflichtige_Aktion_wird_vorgeschlagen_statt_ausgefuehrt()
    {
        var staging = new InMemoryStagingRepository();
        var tool = FakeTool.Returning("ausgeführt");
        var fixture = new EngineFixture(staging: Gate(staging, StagingDecision.Approve)).WithTool(tool);
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

        await fixture.Engine.RunAsync(agent, "sende die Mail", "test-instance", default);

        tool.Invocations.ShouldBeEmpty("die Aktion darf nicht ausgeführt worden sein");
        (await staging.CountPendingAsync(default)).ShouldBe(1, "sie liegt als Vorschlag vor");
    }

    [Fact]
    public async Task Der_Vorschlag_wird_als_Staged_ins_Audit_Log_geschrieben()
    {
        var staging = new InMemoryStagingRepository();
        var audit = new InMemoryAuditRepository();
        var fixture = new EngineFixture(audit, Gate(staging, StagingDecision.Approve))
            .WithTool(FakeTool.Returning("x"));
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

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

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

    [Fact]
    public async Task Eine_gesperrte_Aktion_wird_abgelehnt_und_nicht_ausgefuehrt()
    {
        var staging = new InMemoryStagingRepository();
        var audit = new InMemoryAuditRepository();
        var tool = FakeTool.Returning("x");
        var fixture = new EngineFixture(audit, Gate(staging, StagingDecision.Deny)).WithTool(tool);
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

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

        tool.Invocations.ShouldBeEmpty();
        (await staging.CountPendingAsync(default)).ShouldBe(0, "deny legt keinen Vorschlag an");
        audit.Entries.Single().Status.ShouldBe(AuditStatus.Denied);
    }

    [Fact]
    public async Task Eine_auto_Aktion_laeuft_unveraendert_durch()
    {
        var staging = new InMemoryStagingRepository();
        var tool = FakeTool.Returning("ausgeführt");
        var fixture = new EngineFixture(staging: Gate(staging, StagingDecision.Auto)).WithTool(tool);
        var agent = fixture.AddAgent("agent-a", "TestTool");
        fixture.Client.RespondsWithToolCall("TestTool").RespondsWithText("fertig");

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

        tool.Invocations.Count.ShouldBe(1, "auto führt normal aus");
        (await staging.CountPendingAsync(default)).ShouldBe(0);
    }

    [Fact]
    public async Task Ein_freigegebener_Aufruf_wird_direkt_ausgefuehrt_ohne_erneute_Pruefung()
    {
        // ExecuteApprovedCallAsync umgeht das Gate — genau der eingefrorene Aufruf läuft.
        var staging = new InMemoryStagingRepository();
        var tool = FakeTool.Returning("gesendet");
        var fixture = new EngineFixture(staging: Gate(staging, StagingDecision.Approve)).WithTool(tool);
        fixture.AddAgent("agent-a", "TestTool");

        var result = await fixture.Engine.ExecuteApprovedCallAsync(
            "agent-a", "TestTool", "{\"action\":\"send\"}", "run-x", default);

        result.ShouldBe("gesendet");
        tool.Invocations.Count.ShouldBe(1);
        (await staging.CountPendingAsync(default)).ShouldBe(0, "ein freigegebener Aufruf staged nicht erneut");
    }
}
