using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Tests.Infrastructure;

namespace ClaudeDo.Worker.Tests.Repositories;

public sealed class TaskRunRepositoryTests : IDisposable
{
    private readonly DbFixture _db = new();
    private readonly ClaudeDoDbContext _ctx;
    private readonly TaskRunRepository _runs;
    private readonly string _taskId;

    public TaskRunRepositoryTests()
    {
        _ctx = _db.CreateContext();
        _runs = new TaskRunRepository(_ctx);

        // Seed a list and task for all tests
        var lists = new ListRepository(_ctx);
        var tasks = new TaskRepository(_ctx);
        var listId = Guid.NewGuid().ToString();
        lists.AddAsync(new ListEntity
        {
            Id = listId,
            Name = "Test List",
            CreatedAt = DateTime.UtcNow,
        }).GetAwaiter().GetResult();

        _taskId = Guid.NewGuid().ToString();
        tasks.AddAsync(new TaskEntity
        {
            Id = _taskId,
            ListId = listId,
            Title = "Test Task",
            Status = Data.Models.TaskStatus.Queued,
            CreatedAt = DateTime.UtcNow,
            CommitType = "feat",
        }).GetAwaiter().GetResult();
    }

    public void Dispose()
    {
        _ctx.Dispose();
        _db.Dispose();
    }

    private TaskRunEntity MakeRun(int runNumber, bool isRetry = false) => new()
    {
        Id = Guid.NewGuid().ToString(),
        TaskId = _taskId,
        RunNumber = runNumber,
        IsRetry = isRetry,
        Prompt = $"Do something (run {runNumber})",
        StartedAt = DateTime.UtcNow,
    };

    [Fact]
    public async Task Add_And_GetById_Roundtrips()
    {
        var entity = new TaskRunEntity
        {
            Id = Guid.NewGuid().ToString(),
            TaskId = _taskId,
            RunNumber = 1,
            SessionId = "sess-abc",
            IsRetry = false,
            Prompt = "Fix the bug",
            ResultMarkdown = "All done",
            StructuredOutputJson = """{"ok":true}""",
            ErrorMarkdown = null,
            ExitCode = 0,
            TurnCount = 5,
            TokensIn = 1000,
            TokensOut = 2000,
            LogPath = "/tmp/run1.ndjson",
            StartedAt = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            FinishedAt = new DateTime(2026, 1, 1, 0, 5, 0, DateTimeKind.Utc),
        };

        await _runs.AddAsync(entity);
        var loaded = await _runs.GetByIdAsync(entity.Id);

        Assert.NotNull(loaded);
        Assert.Equal(entity.Id, loaded.Id);
        Assert.Equal(entity.TaskId, loaded.TaskId);
        Assert.Equal(entity.RunNumber, loaded.RunNumber);
        Assert.Equal(entity.SessionId, loaded.SessionId);
        Assert.Equal(entity.IsRetry, loaded.IsRetry);
        Assert.Equal(entity.Prompt, loaded.Prompt);
        Assert.Equal(entity.ResultMarkdown, loaded.ResultMarkdown);
        Assert.Equal(entity.StructuredOutputJson, loaded.StructuredOutputJson);
        Assert.Null(loaded.ErrorMarkdown);
        Assert.Equal(entity.ExitCode, loaded.ExitCode);
        Assert.Equal(entity.TurnCount, loaded.TurnCount);
        Assert.Equal(entity.TokensIn, loaded.TokensIn);
        Assert.Equal(entity.TokensOut, loaded.TokensOut);
        Assert.Equal(entity.LogPath, loaded.LogPath);
        Assert.Equal(entity.StartedAt, loaded.StartedAt);
        Assert.Equal(entity.FinishedAt, loaded.FinishedAt);
    }

    [Fact]
    public async Task GetByTaskId_Returns_Ordered_By_RunNumber()
    {
        var run3 = MakeRun(3);
        var run1 = MakeRun(1);
        var run2 = MakeRun(2);

        await _runs.AddAsync(run3);
        await _runs.AddAsync(run1);
        await _runs.AddAsync(run2);

        var runs = await _runs.GetByTaskIdAsync(_taskId);

        Assert.Equal(3, runs.Count);
        Assert.Equal(1, runs[0].RunNumber);
        Assert.Equal(2, runs[1].RunNumber);
        Assert.Equal(3, runs[2].RunNumber);
    }

    [Fact]
    public async Task GetLatestByTaskId_Returns_Highest_RunNumber()
    {
        var run1 = MakeRun(1);
        var run2 = MakeRun(2);

        await _runs.AddAsync(run1);
        await _runs.AddAsync(run2);

        var latest = await _runs.GetLatestByTaskIdAsync(_taskId);

        Assert.NotNull(latest);
        Assert.Equal(run2.Id, latest.Id);
        Assert.Equal(2, latest.RunNumber);
    }

    [Fact]
    public async Task Update_Persists_Completion_Fields()
    {
        var run = MakeRun(1);
        await _runs.AddAsync(run);

        run.SessionId = "sess-xyz";
        run.ResultMarkdown = "Task completed";
        run.StructuredOutputJson = """{"status":"done"}""";
        run.ErrorMarkdown = null;
        run.ExitCode = 0;
        run.TurnCount = 12;
        run.TokensIn = 5000;
        run.TokensOut = 8000;
        run.FinishedAt = new DateTime(2026, 6, 1, 12, 0, 0, DateTimeKind.Utc);

        await _runs.UpdateAsync(run);
        var loaded = await _runs.GetByIdAsync(run.Id);

        Assert.NotNull(loaded);
        Assert.Equal("sess-xyz", loaded.SessionId);
        Assert.Equal("Task completed", loaded.ResultMarkdown);
        Assert.Equal("""{"status":"done"}""", loaded.StructuredOutputJson);
        Assert.Null(loaded.ErrorMarkdown);
        Assert.Equal(0, loaded.ExitCode);
        Assert.Equal(12, loaded.TurnCount);
        Assert.Equal(5000, loaded.TokensIn);
        Assert.Equal(8000, loaded.TokensOut);
        Assert.Equal(new DateTime(2026, 6, 1, 12, 0, 0, DateTimeKind.Utc), loaded.FinishedAt);
    }

    [Fact]
    public async Task GetLatestByTaskId_Returns_Null_When_No_Runs()
    {
        var latest = await _runs.GetLatestByTaskIdAsync(Guid.NewGuid().ToString());

        Assert.Null(latest);
    }
}
