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

namespace ClaudeDo.Worker.Tests;

public sealed class WaitingForChildrenLifecycleTests : IDisposable
{
    private readonly DbFixture _db = new();
    private readonly TestDbContextFactory _factory;
    private readonly TaskStateServiceBuilder.Built _built;
    private int _numberSeed;

    public WaitingForChildrenLifecycleTests()
    {
        _factory = _db.CreateFactory();
        _built = TaskStateServiceBuilder.Build(_factory);
    }
    public void Dispose() => _db.Dispose();

    private async Task<string> SeedRunningStandaloneAsync()
    {
        using var ctx = _db.CreateContext();
        ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
        ctx.Tasks.Add(new TaskEntity { Id = "p1", ListId = "l1", Title = "Parent", Number = ++_numberSeed,
            Status = TaskStatus.Running, CreatedAt = DateTime.UtcNow });
        await ctx.SaveChangesAsync();
        return "p1";
    }

    [Fact]
    public async Task SubmitForChildren_moves_running_task_to_WaitingForChildren()
    {
        var id = await SeedRunningStandaloneAsync();
        var result = await _built.State.SubmitForChildrenAsync(id, DateTime.UtcNow, "ran ok", default);
        Assert.True(result.Ok);
        using var ctx = _db.CreateContext();
        var t = await new TaskRepository(ctx).GetByIdAsync(id);
        Assert.Equal(TaskStatus.WaitingForChildren, t!.Status);
        Assert.Equal("ran ok", t.Result);
        Assert.NotNull(t.FinishedAt);
    }

    [Fact]
    public async Task SubmitForChildren_rejects_when_not_running()
    {
        var id = await SeedRunningStandaloneAsync();
        await _built.State.SubmitForChildrenAsync(id, DateTime.UtcNow, null, default);
        var second = await _built.State.SubmitForChildrenAsync(id, DateTime.UtcNow, null, default);
        Assert.False(second.Ok);
    }

    private async Task SeedParentWithChildrenAsync(string parentStatus, params TaskStatus[] childStatuses)
    {
        using var ctx = _db.CreateContext();
        if (!ctx.Lists.Any())
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
        ctx.Tasks.Add(new TaskEntity { Id = "par", ListId = "l1", Title = "Parent", Number = ++_numberSeed,
            Status = Enum.Parse<TaskStatus>(parentStatus), Result = "parent ran", CreatedAt = DateTime.UtcNow });
        int i = 0;
        foreach (var cs in childStatuses)
            ctx.Tasks.Add(new TaskEntity { Id = $"c{i++}", ListId = "l1", Title = "Child", Number = ++_numberSeed,
                Status = cs, ParentTaskId = "par", CreatedAt = DateTime.UtcNow });
        await ctx.SaveChangesAsync();
    }

    [Fact]
    public async Task LastChildDone_advances_WaitingForChildren_parent_to_WaitingForReview()
    {
        await SeedParentWithChildrenAsync("WaitingForChildren", TaskStatus.Done, TaskStatus.Running);
        await _built.State.CompleteAsync("c1", DateTime.UtcNow, "child ok", default);
        using var ctx = _db.CreateContext();
        var parent = await new TaskRepository(ctx).GetByIdAsync("par");
        Assert.Equal(TaskStatus.WaitingForReview, parent!.Status);
    }

    [Fact]
    public async Task NonLastChild_leaves_parent_in_WaitingForChildren()
    {
        await SeedParentWithChildrenAsync("WaitingForChildren", TaskStatus.Running, TaskStatus.Idle);
        await _built.State.CompleteAsync("c0", DateTime.UtcNow, "ok", default);
        using var ctx = _db.CreateContext();
        var parent = await new TaskRepository(ctx).GetByIdAsync("par");
        Assert.Equal(TaskStatus.WaitingForChildren, parent!.Status);
    }

    [Fact]
    public async Task FailedChild_still_advances_parent_and_flags_failure()
    {
        await SeedParentWithChildrenAsync("WaitingForChildren", TaskStatus.Done, TaskStatus.Running);
        await _built.State.FailAsync("c1", DateTime.UtcNow, "boom", default);
        using var ctx = _db.CreateContext();
        var parent = await new TaskRepository(ctx).GetByIdAsync("par");
        Assert.Equal(TaskStatus.WaitingForReview, parent!.Status);
        Assert.Contains("1 failed", parent.Result!);
    }

    private async Task SeedParentChildPairAsync(PlanningPhase parentPhase)
    {
        using var ctx = _db.CreateContext();
        if (!ctx.Lists.Any())
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
        ctx.Tasks.Add(new TaskEntity { Id = "par", ListId = "l1", Title = "Parent", Number = ++_numberSeed,
            Status = TaskStatus.WaitingForChildren, PlanningPhase = parentPhase, CreatedAt = DateTime.UtcNow });
        ctx.Tasks.Add(new TaskEntity { Id = "kid", ListId = "l1", Title = "Child", Number = ++_numberSeed,
            Status = TaskStatus.Idle, ParentTaskId = "par", CreatedAt = DateTime.UtcNow });
        await ctx.SaveChangesAsync();
    }

    [Fact]
    public async Task ImprovementChild_can_be_enqueued()
    {
        await SeedParentChildPairAsync(PlanningPhase.None);
        var result = await _built.State.EnqueueAsync("kid", default);
        Assert.True(result.Ok);
        using var ctx = _db.CreateContext();
        Assert.Equal(TaskStatus.Queued, (await new TaskRepository(ctx).GetByIdAsync("kid"))!.Status);
    }

    [Fact]
    public async Task PlanningDraftChild_cannot_be_enqueued()
    {
        await SeedParentChildPairAsync(PlanningPhase.Active);
        var result = await _built.State.EnqueueAsync("kid", default);
        Assert.False(result.Ok);
    }

    [Fact]
    public async Task SequentialChildren_parent_only_advances_after_both_are_terminal()
    {
        // Arrange: parent WaitingForChildren, child1 Done, child2 blocked by child1 (Running)
        using (var ctx = _db.CreateContext())
        {
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "par", ListId = "l1", Title = "Parent", Number = ++_numberSeed,
                Status = TaskStatus.WaitingForChildren, CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "c1", ListId = "l1", Title = "Child1", Number = ++_numberSeed,
                Status = TaskStatus.Running, ParentTaskId = "par", CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "c2", ListId = "l1", Title = "Child2", Number = ++_numberSeed,
                Status = TaskStatus.Running, ParentTaskId = "par", BlockedByTaskId = "c1",
                CreatedAt = DateTime.UtcNow });
            await ctx.SaveChangesAsync();
        }

        // Act: complete child1 — parent must stay WaitingForChildren because child2 is still running
        await _built.State.CompleteAsync("c1", DateTime.UtcNow, "c1 ok", default);
        using (var ctx = _db.CreateContext())
        {
            var par = await new TaskRepository(ctx).GetByIdAsync("par");
            Assert.Equal(TaskStatus.WaitingForChildren, par!.Status);
        }

        // Act: complete child2 — now all children are terminal; parent must advance
        await _built.State.CompleteAsync("c2", DateTime.UtcNow, "c2 ok", default);
        using (var ctx = _db.CreateContext())
        {
            var par = await new TaskRepository(ctx).GetByIdAsync("par");
            Assert.Equal(TaskStatus.WaitingForReview, par!.Status);
        }
    }

    [Fact]
    public async Task WaitingForChildren_parent_can_be_cancelled()
    {
        using (var ctx = _db.CreateContext())
        {
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "par", ListId = "l1", Title = "Parent", Number = ++_numberSeed,
                Status = TaskStatus.WaitingForChildren, CreatedAt = DateTime.UtcNow });
            await ctx.SaveChangesAsync();
        }

        var result = await _built.State.CancelAsync("par", DateTime.UtcNow, default);

        Assert.True(result.Ok);
        using (var ctx = _db.CreateContext())
        {
            var par = await new TaskRepository(ctx).GetByIdAsync("par");
            Assert.Equal(TaskStatus.Cancelled, par!.Status);
        }
    }

    [Fact]
    public async Task Cancelling_WaitingForChildren_parent_cascades_to_nonterminal_children()
    {
        using (var ctx = _db.CreateContext())
        {
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "par", ListId = "l1", Title = "Parent", Number = ++_numberSeed,
                Status = TaskStatus.WaitingForChildren, CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "c_running", ListId = "l1", Title = "Running child", Number = ++_numberSeed,
                Status = TaskStatus.Running, ParentTaskId = "par", CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "c_queued", ListId = "l1", Title = "Queued child", Number = ++_numberSeed,
                Status = TaskStatus.Queued, ParentTaskId = "par", BlockedByTaskId = "c_running",
                CreatedAt = DateTime.UtcNow });
            await ctx.SaveChangesAsync();
        }

        var result = await _built.State.CancelAsync("par", DateTime.UtcNow, default);

        Assert.True(result.Ok);
        using var ctx2 = _db.CreateContext();
        var repo = new TaskRepository(ctx2);
        var parent = await repo.GetByIdAsync("par");
        var runningChild = await repo.GetByIdAsync("c_running");
        var queuedChild = await repo.GetByIdAsync("c_queued");

        Assert.Equal(TaskStatus.Cancelled, parent!.Status);
        Assert.Equal(TaskStatus.Cancelled, runningChild!.Status);
        Assert.Equal(TaskStatus.Cancelled, queuedChild!.Status);
        Assert.Null(queuedChild.BlockedByTaskId);
    }

    [Fact]
    public async Task Cancelling_WaitingForChildren_parent_stops_running_child_process()
    {
        using (var ctx = _db.CreateContext())
        {
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "par", ListId = "l1", Title = "Parent", Number = ++_numberSeed,
                Status = TaskStatus.WaitingForChildren, CreatedAt = DateTime.UtcNow });
            ctx.Tasks.Add(new TaskEntity { Id = "c_running", ListId = "l1", Title = "Running child", Number = ++_numberSeed,
                Status = TaskStatus.Running, ParentTaskId = "par", CreatedAt = DateTime.UtcNow });
            await ctx.SaveChangesAsync();
        }

        using var childCts = new CancellationTokenSource();
        _built.RunCancels.Register("c_running", childCts);

        var result = await _built.State.CancelAsync("par", DateTime.UtcNow, default);

        Assert.True(result.Ok);
        Assert.True(childCts.IsCancellationRequested);
    }

    [Fact]
    public async Task Cancelling_running_task_signals_its_registered_run()
    {
        var id = await SeedRunningStandaloneAsync();
        using var cts = new CancellationTokenSource();
        _built.RunCancels.Register(id, cts);

        var result = await _built.State.CancelAsync(id, DateTime.UtcNow, default);

        Assert.True(result.Ok);
        Assert.True(cts.IsCancellationRequested);
    }

    // ─── FinalizePlanningAsync ────────────────────────────────────────────

    private async Task<string> SeedActivePlanningParentAsync(string id = "par")
    {
        using var ctx = _db.CreateContext();
        if (!ctx.Lists.Any())
            ctx.Lists.Add(new ListEntity { Id = "l1", Name = "L", CreatedAt = DateTime.UtcNow });
        ctx.Tasks.Add(new TaskEntity
        {
            Id = id,
            ListId = "l1",
            Title = "Parent",
            Number = ++_numberSeed,
            Status = TaskStatus.Idle,
            PlanningPhase = PlanningPhase.Active,
            CreatedAt = DateTime.UtcNow,
        });
        await ctx.SaveChangesAsync();
        return id;
    }

    private async Task SeedChildAsync(string parentId, string childId, TaskStatus status = TaskStatus.Idle)
    {
        using var ctx = _db.CreateContext();
        ctx.Tasks.Add(new TaskEntity
        {
            Id = childId,
            ListId = "l1",
            Title = "Child",
            Number = ++_numberSeed,
            Status = status,
            ParentTaskId = parentId,
            CreatedAt = DateTime.UtcNow,
        });
        await ctx.SaveChangesAsync();
    }

    [Fact]
    public async Task FinalizePlanning_WithChildren_SetsWaitingForChildren_AndPlanningFinalized()
    {
        await SeedActivePlanningParentAsync();
        await SeedChildAsync("par", "c1");
        await SeedChildAsync("par", "c2");

        var result = await _built.State.FinalizePlanningAsync("par", default);

        Assert.True(result.Ok);
        using var ctx = _db.CreateContext();
        var parent = await new TaskRepository(ctx).GetByIdAsync("par");
        Assert.Equal(TaskStatus.WaitingForChildren, parent!.Status);
        Assert.Equal(PlanningPhase.Finalized, parent.PlanningPhase);
    }

    [Fact]
    public async Task FinalizePlanning_WithChildren_ThenAllChildrenTerminal_AdvancesToWaitingForReview()
    {
        await SeedActivePlanningParentAsync();
        await SeedChildAsync("par", "c1");
        await SeedChildAsync("par", "c2");

        await _built.State.FinalizePlanningAsync("par", default);

        // Manually transition parent to WaitingForChildren so CompleteAsync works on the children.
        // Children are Idle — complete them by seeding them as Running first.
        using (var ctx = _db.CreateContext())
        {
            await ctx.Tasks.Where(t => t.Id == "c1").ExecuteUpdateAsync(s => s.SetProperty(t => t.Status, TaskStatus.Running));
            await ctx.Tasks.Where(t => t.Id == "c2").ExecuteUpdateAsync(s => s.SetProperty(t => t.Status, TaskStatus.Running));
        }

        await _built.State.CompleteAsync("c1", DateTime.UtcNow, "ok", default);
        using (var ctx = _db.CreateContext())
            Assert.Equal(TaskStatus.WaitingForChildren, (await new TaskRepository(ctx).GetByIdAsync("par"))!.Status);

        await _built.State.CompleteAsync("c2", DateTime.UtcNow, "ok", default);
        using (var ctx = _db.CreateContext())
            Assert.Equal(TaskStatus.WaitingForReview, (await new TaskRepository(ctx).GetByIdAsync("par"))!.Status);
    }

    [Fact]
    public async Task FinalizePlanning_WithNoChildren_SetsWaitingForReview_Directly()
    {
        await SeedActivePlanningParentAsync();

        var result = await _built.State.FinalizePlanningAsync("par", default);

        Assert.True(result.Ok);
        using var ctx = _db.CreateContext();
        var parent = await new TaskRepository(ctx).GetByIdAsync("par");
        Assert.Equal(TaskStatus.WaitingForReview, parent!.Status);
        Assert.Equal(PlanningPhase.Finalized, parent.PlanningPhase);
    }
}
