using ClawdDotNet.Core.Audit;
using ClawdDotNet.Core.Storage;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Audit;

/// <summary>
/// A3: das append-only Audit-Log und die Receipts. Gegen echte SQLite, weil Schema und
/// Persistenz der Punkt sind.
/// </summary>
public sealed class AuditRepositoryTests : IDisposable
{
    private readonly string _directory;
    private readonly SqliteStorage _storage;
    private readonly SqliteAuditRepository _repo;

    public AuditRepositoryTests()
    {
        _directory = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
        _storage = new SqliteStorage(Path.Combine(_directory, "state.db"));
        _repo = new SqliteAuditRepository(_storage);
    }

    public void Dispose()
    {
        Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools();
        try { Directory.Delete(_directory, recursive: true); }
        catch { /* Aufräumen ist Nebensache */ }
    }

    private AuditEntry Entry(string runId, string tool, AuditStatus status = AuditStatus.Ok) => new()
    {
        RunId = runId,
        AgentId = "agent-a",
        Model = "test/model",
        Source = "task",
        Tool = tool,
        Arguments = "{\"x\":1}",
        Status = status,
        Summary = "",
        DurationMs = 5,
        OccurredAt = DateTime.UtcNow
    };

    [Fact]
    public async Task Ein_Eintrag_wird_geschrieben_und_wiedergefunden()
    {
        await _repo.AppendAsync(Entry("run-1", "FileRW"), default);

        var recent = await _repo.ListRecentAsync(10, default);
        recent.Count.ShouldBe(1);
        recent[0].Tool.ShouldBe("FileRW");
        recent[0].RunId.ShouldBe("run-1");
        (await _repo.CountAsync(default)).ShouldBe(1);
    }

    [Fact]
    public async Task Die_Aufrufe_eines_Laufs_kommen_in_Reihenfolge()
    {
        await _repo.AppendAsync(Entry("run-1", "Memory"), default);
        await _repo.AppendAsync(Entry("run-1", "FileRW"), default);
        await _repo.AppendAsync(Entry("run-2", "Mail"), default);

        var forRun = await _repo.ListForRunAsync("run-1", default);
        forRun.Select(e => e.Tool).ShouldBe(["Memory", "FileRW"]);
    }

    [Fact]
    public async Task Die_juengsten_Eintraege_kommen_zuerst()
    {
        await _repo.AppendAsync(Entry("run-1", "erst"), default);
        await _repo.AppendAsync(Entry("run-1", "zuletzt"), default);

        (await _repo.ListRecentAsync(10, default))[0].Tool.ShouldBe("zuletzt");
    }

    [Fact]
    public async Task Der_Status_ueberlebt_den_Rundlauf()
    {
        await _repo.AppendAsync(Entry("r", "X", AuditStatus.Denied), default);
        (await _repo.ListRecentAsync(1, default))[0].Status.ShouldBe(AuditStatus.Denied);
    }

    // ─── Receipts ───

    [Fact]
    public async Task Ein_Receipt_verknuepft_Lauf_und_Task()
    {
        await _repo.RecordReceiptAsync(new RunReceipt
        {
            RunId = "run-9",
            AgentId = "agent-a",
            Model = "test/model",
            Source = "task",
            TaskId = "t-42",
            Status = "Completed",
            StepCount = 3,
            PromptTokens = 100,
            CompletionTokens = 20,
            CachedTokens = 10,
            CostUsd = 0.0123m,
            CostIsKnown = true,
            DurationMs = 1200,
            ResultRef = "fertig",
            OccurredAt = DateTime.UtcNow
        }, default);

        var byRun = await _repo.GetReceiptForRunAsync("run-9", default);
        byRun.ShouldNotBeNull();
        byRun!.TaskId.ShouldBe("t-42");
        byRun.CostUsd.ShouldBe(0.0123m);
        byRun.CostIsKnown.ShouldBeTrue();

        var byTask = await _repo.ListReceiptsForTaskAsync("t-42", default);
        byTask.Count.ShouldBe(1);
        byTask[0].RunId.ShouldBe("run-9");
    }

    [Fact]
    public async Task Eintraege_ueberdauern_das_Schliessen_der_Datenbank()
    {
        await _repo.AppendAsync(Entry("run-1", "FileRW"), default);

        var reopened = new SqliteAuditRepository(new SqliteStorage(Path.Combine(_directory, "state.db")));
        (await reopened.CountAsync(default)).ShouldBe(1);
    }
}
