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

namespace ClaudeDo.Worker.Tests.Repositories;

public sealed class TaskRepositoryTests : IDisposable
{
    private readonly DbFixture _db = new();
    private readonly ClaudeDoDbContext _ctx;
    private readonly TaskRepository _tasks;
    private readonly ListRepository _lists;

    public TaskRepositoryTests()
    {
        _ctx = _db.CreateContext();
        _tasks = new TaskRepository(_ctx);
        _lists = new ListRepository(_ctx);
    }

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

    private async Task<string> CreateListAsync(string? id = null)
    {
        var listId = id ?? Guid.NewGuid().ToString();
        await _lists.AddAsync(new ListEntity
        {
            Id = listId,
            Name = "Test List",
            CreatedAt = DateTime.UtcNow,
        });
        return listId;
    }

    private TaskEntity MakeTask(string listId, TaskStatus status = TaskStatus.Queued, DateTime? createdAt = null, DateTime? scheduledFor = null) => new()
    {
        Id = Guid.NewGuid().ToString(),
        ListId = listId,
        Title = "Test Task",
        Description = "A description",
        Status = status,
        ScheduledFor = scheduledFor,
        CreatedAt = createdAt ?? DateTime.UtcNow,
        CommitType = "feat",
    };

    [Fact]
    public async Task AddAsync_Roundtrips_AllFields()
    {
        var listId = await CreateListAsync();
        var entity = new TaskEntity
        {
            Id = Guid.NewGuid().ToString(),
            ListId = listId,
            Title = "My Task",
            Description = "Desc",
            Status = TaskStatus.Queued,
            ScheduledFor = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc),
            Result = "some result",
            LogPath = "/tmp/log.ndjson",
            CreatedAt = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            StartedAt = new DateTime(2026, 1, 1, 1, 0, 0, DateTimeKind.Utc),
            FinishedAt = new DateTime(2026, 1, 1, 2, 0, 0, DateTimeKind.Utc),
            CommitType = "feat",
        };

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

        Assert.NotNull(loaded);
        Assert.Equal(entity.Id, loaded.Id);
        Assert.Equal(entity.ListId, loaded.ListId);
        Assert.Equal(entity.Title, loaded.Title);
        Assert.Equal(entity.Description, loaded.Description);
        Assert.Equal(entity.Status, loaded.Status);
        Assert.Equal(entity.ScheduledFor!.Value.Date, loaded.ScheduledFor!.Value.Date);
        Assert.Equal(entity.Result, loaded.Result);
        Assert.Equal(entity.LogPath, loaded.LogPath);
        Assert.Equal(entity.CommitType, loaded.CommitType);
    }

    [Fact]
    public async Task Transitions_MarkRunning_ThenMarkDone()
    {
        var listId = await CreateListAsync();
        var task = MakeTask(listId);
        await _tasks.AddAsync(task);

        var startedAt = DateTime.UtcNow;
        await _tasks.MarkRunningAsync(task.Id, startedAt);
        var running = await _tasks.GetByIdAsync(task.Id);
        Assert.Equal(TaskStatus.Running, running!.Status);
        Assert.NotNull(running.StartedAt);

        var finishedAt = DateTime.UtcNow;
        await _tasks.MarkDoneAsync(task.Id, finishedAt, "All good");
        var done = await _tasks.GetByIdAsync(task.Id);
        Assert.Equal(TaskStatus.Done, done!.Status);
        Assert.Equal("All good", done.Result);
        Assert.NotNull(done.FinishedAt);
    }

    [Fact]
    public async Task FlipAllRunningToFailedAsync_FlipsOnlyRunningRows()
    {
        var listId = await CreateListAsync();
        var running1 = MakeTask(listId, status: TaskStatus.Running);
        var running2 = MakeTask(listId, status: TaskStatus.Running);
        var queued = MakeTask(listId, status: TaskStatus.Queued);
        var done = MakeTask(listId, status: TaskStatus.Done);

        await _tasks.AddAsync(running1);
        await _tasks.AddAsync(running2);
        await _tasks.AddAsync(queued);
        await _tasks.AddAsync(done);

        var flipped = await _tasks.FlipAllRunningToFailedAsync("worker restart");

        Assert.Equal(2, flipped);

        var r1 = await _tasks.GetByIdAsync(running1.Id);
        Assert.Equal(TaskStatus.Failed, r1!.Status);
        Assert.StartsWith("[stale] ", r1.Result);

        var r2 = await _tasks.GetByIdAsync(running2.Id);
        Assert.Equal(TaskStatus.Failed, r2!.Status);

        var q = await _tasks.GetByIdAsync(queued.Id);
        Assert.Equal(TaskStatus.Queued, q!.Status);

        var d = await _tasks.GetByIdAsync(done.Id);
        Assert.Equal(TaskStatus.Done, d!.Status);
    }

    [Fact]
    public async Task ResetToManualAsync_ClearsResultFields_AndSetsStatusManual()
    {
        var listId = await CreateListAsync();
        var task = new TaskEntity
        {
            Id = Guid.NewGuid().ToString(),
            ListId = listId,
            Title = "T",
            CreatedAt = DateTime.UtcNow,
            Status = TaskStatus.Failed,
            StartedAt = DateTime.UtcNow.AddMinutes(-5),
            FinishedAt = DateTime.UtcNow,
            Result = "boom",
            CommitType = "feat",
        };
        await _tasks.AddAsync(task);

        await _tasks.ResetToManualAsync(task.Id);

        using var readCtx = _db.CreateContext();
        var after = await new TaskRepository(readCtx).GetByIdAsync(task.Id);
        Assert.NotNull(after);
        Assert.Equal(TaskStatus.Idle, after!.Status);
        Assert.Null(after.StartedAt);
        Assert.Null(after.FinishedAt);
        Assert.Null(after.Result);
    }

    [Fact]
    public async Task AddAsync_AssignsDense_PerList_SortOrder()
    {
        var listA = await CreateListAsync();
        var listB = await CreateListAsync();

        var a0 = MakeTask(listA); await _tasks.AddAsync(a0);
        var a1 = MakeTask(listA); await _tasks.AddAsync(a1);
        var b0 = MakeTask(listB); await _tasks.AddAsync(b0);
        var a2 = MakeTask(listA); await _tasks.AddAsync(a2);

        var reloadA0 = await _tasks.GetByIdAsync(a0.Id);
        var reloadA1 = await _tasks.GetByIdAsync(a1.Id);
        var reloadA2 = await _tasks.GetByIdAsync(a2.Id);
        var reloadB0 = await _tasks.GetByIdAsync(b0.Id);

        Assert.Equal(0, reloadA0!.SortOrder);
        Assert.Equal(1, reloadA1!.SortOrder);
        Assert.Equal(2, reloadA2!.SortOrder);
        Assert.Equal(0, reloadB0!.SortOrder);
    }

    [Fact]
    public async Task GetByListIdAsync_OrdersBy_SortOrder()
    {
        var listId = await CreateListAsync();
        var t0 = MakeTask(listId); await _tasks.AddAsync(t0);
        var t1 = MakeTask(listId); await _tasks.AddAsync(t1);
        var t2 = MakeTask(listId); await _tasks.AddAsync(t2);

        await _tasks.ReorderAsync(listId, new[] { t2.Id, t0.Id, t1.Id });

        var list = await _tasks.GetByListIdAsync(listId);
        Assert.Equal(new[] { t2.Id, t0.Id, t1.Id }, list.Select(t => t.Id).ToArray());
    }

    [Fact]
    public async Task ReorderAsync_Renumbers_Dense()
    {
        var listId = await CreateListAsync();
        var t0 = MakeTask(listId); await _tasks.AddAsync(t0);
        var t1 = MakeTask(listId); await _tasks.AddAsync(t1);
        var t2 = MakeTask(listId); await _tasks.AddAsync(t2);

        await _tasks.ReorderAsync(listId, new[] { t1.Id, t2.Id, t0.Id });

        var r0 = await _tasks.GetByIdAsync(t0.Id);
        var r1 = await _tasks.GetByIdAsync(t1.Id);
        var r2 = await _tasks.GetByIdAsync(t2.Id);

        Assert.Equal(2, r0!.SortOrder);
        Assert.Equal(0, r1!.SortOrder);
        Assert.Equal(1, r2!.SortOrder);
    }

    [Fact]
    public async Task ReorderAsync_IgnoresIds_FromOtherLists()
    {
        var listA = await CreateListAsync();
        var listB = await CreateListAsync();
        var a0 = MakeTask(listA); await _tasks.AddAsync(a0);
        var b0 = MakeTask(listB); await _tasks.AddAsync(b0);

        // b0 does not belong to listA and should not be renumbered there.
        await _tasks.ReorderAsync(listA, new[] { b0.Id, a0.Id });

        var reloadB = await _tasks.GetByIdAsync(b0.Id);
        Assert.Equal(0, reloadB!.SortOrder);
    }

}
