using ClawdDotNet.Core.Staging;
using ClawdDotNet.Core.Storage;
using Shouldly;

namespace ClawdDotNet.Core.Tests.Staging;

/// <summary>
/// Der bedingte Statuswechsel ist der Kern: zwei Reviewer dürfen denselben Vorschlag nicht
/// doppelt entscheiden. Getestet gegen echte SQLite.
/// </summary>
public sealed class StagingRepositoryTests : IDisposable
{
    private readonly string _directory;
    private readonly SqliteStorage _storage;
    private readonly SqliteStagingRepository _repo;

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

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

    private Task<long> Stage(string tool = "Mail", string action = "send")
        => _repo.AppendAsync(new StagedCall
        {
            RunId = "run-1",
            AgentId = "agent-a",
            Tool = tool,
            Action = action,
            ArgumentsJson = "{\"action\":\"send\",\"to\":\"x@y.de\"}",
            Proposal = $"{tool}.{action}"
        }, default);

    [Fact]
    public async Task Ein_Vorschlag_wird_angelegt_und_erscheint_als_offen()
    {
        var id = await Stage();

        var pending = await _repo.ListPendingAsync(default);
        pending.Count.ShouldBe(1);
        pending[0].Id.ShouldBe(id);
        pending[0].Status.ShouldBe(StagingStatus.Pending);
        (await _repo.CountPendingAsync(default)).ShouldBe(1);
    }

    [Fact]
    public async Task Der_eingefrorene_Argument_JSON_bleibt_erhalten()
    {
        var id = await Stage();
        (await _repo.GetAsync(id, default))!.ArgumentsJson.ShouldContain("x@y.de");
    }

    [Fact]
    public async Task Nur_ein_Reviewer_kann_denselben_Vorschlag_entscheiden()
    {
        var id = await Stage();

        var results = await Task.WhenAll(
            Enumerable.Range(0, 16).Select(i =>
                _repo.TryTransitionAsync(id, StagingStatus.Pending, StagingStatus.Approved,
                    $"user-{i}", null, DateTime.UtcNow, default)));

        results.Count(won => won).ShouldBe(1, "genau ein Reviewer gewinnt den Übergang");
    }

    [Fact]
    public async Task Ein_bereits_entschiedener_Vorschlag_laesst_sich_nicht_erneut_wechseln()
    {
        var id = await Stage();
        (await _repo.TryTransitionAsync(id, StagingStatus.Pending, StagingStatus.Rejected, "u", "nein", DateTime.UtcNow, default)).ShouldBeTrue();
        (await _repo.TryTransitionAsync(id, StagingStatus.Pending, StagingStatus.Approved, "u2", null, DateTime.UtcNow, default))
            .ShouldBeFalse("er ist nicht mehr pending");

        (await _repo.ListPendingAsync(default)).ShouldBeEmpty();
    }

    [Fact]
    public async Task Finalize_setzt_Endstatus_und_Ergebnis()
    {
        var id = await Stage();
        await _repo.TryTransitionAsync(id, StagingStatus.Pending, StagingStatus.Approved, "u", null, DateTime.UtcNow, default);
        await _repo.FinalizeAsync(id, StagingStatus.Executed, "gesendet", DateTime.UtcNow, default);

        var call = await _repo.GetAsync(id, default);
        call!.Status.ShouldBe(StagingStatus.Executed);
        call.ResultRef.ShouldBe("gesendet");
    }
}
