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

namespace ClaudeDo.Worker.Tests.Online;

public sealed class OnlineBacklogTests : IDisposable
{
    private readonly DbFixture _db = new();
    private readonly ClaudeDoDbContext _ctx;
    private readonly TaskRepository _tasks;
    private readonly ListRepository _lists;
    private string _listId = null!;

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

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

    private async Task SeedListAsync()
    {
        _listId = Guid.NewGuid().ToString();
        await _lists.AddAsync(new ListEntity { Id = _listId, Name = "Test", CreatedAt = DateTime.UtcNow });
    }

    private TaskEntity Make(
        TaskStatus status = TaskStatus.Idle,
        string? parentId = null,
        PlanningPhase planning = PlanningPhase.None,
        string? blockedById = null) => new()
    {
        Id = Guid.NewGuid().ToString(),
        ListId = _listId,
        Title = "T",
        Status = status,
        ParentTaskId = parentId,
        PlanningPhase = planning,
        BlockedByTaskId = blockedById,
        CreatedAt = DateTime.UtcNow,
    };

    [Fact]
    public async Task CurrentAsync_Returns_OnlyIdleBacklogItems()
    {
        await SeedListAsync();

        var idle = Make(TaskStatus.Idle);
        var queued = Make(TaskStatus.Queued);
        var running = Make(TaskStatus.Running);
        var done = Make(TaskStatus.Done);
        var failed = Make(TaskStatus.Failed);

        // Idle but with parent → planning child
        var parent = Make(TaskStatus.Idle);
        await _tasks.AddAsync(parent);
        var child = Make(TaskStatus.Idle, parentId: parent.Id);

        // Idle but with PlanningPhase
        var planningParent = Make(TaskStatus.Idle, planning: PlanningPhase.Active);

        // Idle but blocked
        await _tasks.AddAsync(idle);
        var blocker = Make(TaskStatus.Idle);
        await _tasks.AddAsync(blocker);
        var blocked = Make(TaskStatus.Idle, blockedById: blocker.Id);

        await _tasks.AddAsync(queued);
        await _tasks.AddAsync(running);
        await _tasks.AddAsync(done);
        await _tasks.AddAsync(failed);
        await _tasks.AddAsync(child);
        await _tasks.AddAsync(planningParent);
        await _tasks.AddAsync(blocked);

        var mirror = await OnlineBacklog.CurrentAsync(_tasks);

        // Only the plain idle task and the blocker (which itself is plain idle) should appear
        var ids = mirror.Select(m => m.Id).ToHashSet();
        Assert.Contains(idle.Id, ids);
        Assert.Contains(blocker.Id, ids);
        Assert.Contains(parent.Id, ids); // parent with no parent itself is a backlog item

        Assert.DoesNotContain(queued.Id, ids);
        Assert.DoesNotContain(running.Id, ids);
        Assert.DoesNotContain(done.Id, ids);
        Assert.DoesNotContain(failed.Id, ids);
        Assert.DoesNotContain(child.Id, ids);
        Assert.DoesNotContain(planningParent.Id, ids);
        Assert.DoesNotContain(blocked.Id, ids);
    }

    [Fact]
    public void IsBacklogItem_Predicate_FiltersCorrectly()
    {
        Assert.True(OnlineBacklog.IsBacklogItem(new TaskEntity
        {
            Id = "1", ListId = "l", Title = "T",
            Status = TaskStatus.Idle, ParentTaskId = null,
            PlanningPhase = PlanningPhase.None, BlockedByTaskId = null,
            CreatedAt = DateTime.UtcNow,
        }));
        Assert.False(OnlineBacklog.IsBacklogItem(new TaskEntity
        {
            Id = "2", ListId = "l", Title = "T",
            Status = TaskStatus.Queued, ParentTaskId = null,
            PlanningPhase = PlanningPhase.None, BlockedByTaskId = null,
            CreatedAt = DateTime.UtcNow,
        }));
        Assert.False(OnlineBacklog.IsBacklogItem(new TaskEntity
        {
            Id = "3", ListId = "l", Title = "T",
            Status = TaskStatus.Idle, ParentTaskId = "p",
            PlanningPhase = PlanningPhase.None, BlockedByTaskId = null,
            CreatedAt = DateTime.UtcNow,
        }));
        Assert.False(OnlineBacklog.IsBacklogItem(new TaskEntity
        {
            Id = "4", ListId = "l", Title = "T",
            Status = TaskStatus.Idle, ParentTaskId = null,
            PlanningPhase = PlanningPhase.Active, BlockedByTaskId = null,
            CreatedAt = DateTime.UtcNow,
        }));
        Assert.False(OnlineBacklog.IsBacklogItem(new TaskEntity
        {
            Id = "5", ListId = "l", Title = "T",
            Status = TaskStatus.Idle, ParentTaskId = null,
            PlanningPhase = PlanningPhase.None, BlockedByTaskId = "b",
            CreatedAt = DateTime.UtcNow,
        }));
    }

    [Fact]
    public async Task CurrentAsync_EmptyDb_ReturnsEmpty()
    {
        await SeedListAsync();
        var mirror = await OnlineBacklog.CurrentAsync(_tasks);
        Assert.Empty(mirror);
    }

    [Fact]
    public async Task CurrentAsync_MapsFieldsCorrectly()
    {
        await SeedListAsync();
        var task = Make(TaskStatus.Idle);
        task.Description = "desc";
        await _tasks.AddAsync(task);

        var mirror = await OnlineBacklog.CurrentAsync(_tasks);

        Assert.Single(mirror);
        Assert.Equal(task.Id, mirror[0].Id);
        Assert.Equal(_listId, mirror[0].ListId);
        Assert.Equal("T", mirror[0].Title);
        Assert.Equal("desc", mirror[0].Description);
    }
}
