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

namespace ClaudeDo.Worker.Tests.External;

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

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

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

    private HandoffMcpTools BuildSut(HandoffRoundTracker? tracker = null) =>
        new(_tasks, new HubBroadcaster(_hubContext), tracker ?? new HandoffRoundTracker());

    private async Task<TaskEntity> SeedTaskAsync(string listId, TaskStatus status = TaskStatus.Idle, string title = "t")
    {
        var task = new TaskEntity
        {
            Id = Guid.NewGuid().ToString(), ListId = listId, Title = title,
            Status = status, CreatedAt = DateTime.UtcNow, CommitType = "chore",
        };
        await _tasks.AddAsync(task);
        return task;
    }

    private async Task<string> SeedListAsync()
    {
        var listId = Guid.NewGuid().ToString();
        await _lists.AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow });
        return listId;
    }

    [Fact]
    public async Task HandoffListHandler_ValidIds_BroadcastsAndReturnsCount()
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId, title: "List handler: Alpha");
        var survivor = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor");

        var sut = BuildSut();
        var result = await sut.HandoffListHandler(handlerTask.Id, new[] { survivor.Id }, cancellationToken: CancellationToken.None);

        Assert.True(result.Requested);
        Assert.Equal(handlerTask.Id, result.TaskId);
        Assert.Equal(1, result.SurvivingCount);
        Assert.Equal("wait", result.NextPhase);

        var call = Assert.Single(_hubContext.Proxy.Calls);
        Assert.Equal("HandoffRequested", call.Method);
        Assert.Equal(handlerTask.Id, call.Args[0]);
        Assert.Equal("wait", call.Args[2]);
    }

    [Fact]
    public async Task HandoffListHandler_NextPhaseDefaultsToWait()
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId);
        var survivor = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor");

        var sut = BuildSut();
        var result = await sut.HandoffListHandler(handlerTask.Id, new[] { survivor.Id }, cancellationToken: CancellationToken.None);

        Assert.Equal("wait", result.NextPhase);
    }

    [Theory]
    [InlineData("wait")]
    [InlineData("merge")]
    [InlineData("wait_final")]
    [InlineData("merge_final")]
    public async Task HandoffListHandler_AcceptsEveryKnownPhase(string phase)
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId);
        var survivor = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor");

        var sut = BuildSut();
        var result = await sut.HandoffListHandler(handlerTask.Id, new[] { survivor.Id }, phase, CancellationToken.None);

        Assert.Equal(phase, result.NextPhase);
    }

    [Fact]
    public async Task HandoffListHandler_UnknownPhase_Throws()
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId);
        var survivor = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor");

        var sut = BuildSut();
        var ex = await Assert.ThrowsAsync<ArgumentException>(() =>
            sut.HandoffListHandler(handlerTask.Id, new[] { survivor.Id }, "phase-9", CancellationToken.None));
        Assert.Contains("phase-9", ex.Message);
    }

    [Fact]
    public async Task HandoffListHandler_EmptySurvivingIds_Throws()
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId);

        var sut = BuildSut();
        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            sut.HandoffListHandler(handlerTask.Id, Array.Empty<string>(), cancellationToken: CancellationToken.None));
    }

    [Fact]
    public async Task HandoffListHandler_UnknownHandlerTask_Throws()
    {
        var sut = BuildSut();
        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            sut.HandoffListHandler("missing", new[] { "x" }, cancellationToken: CancellationToken.None));
    }

    [Fact]
    public async Task HandoffListHandler_UnknownSurvivingTask_Throws()
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId);

        var sut = BuildSut();
        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            sut.HandoffListHandler(handlerTask.Id, new[] { "missing" }, cancellationToken: CancellationToken.None));
    }

    [Fact]
    public async Task HandoffListHandler_NormalWaitMergeWaitFinalMergeFinalSequence_PassesUntouched()
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId);
        var survivor = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor");
        var tracker = new HandoffRoundTracker();
        var survivors = new[] { survivor.Id };

        foreach (var phase in new[] { "wait", "merge", "wait_final", "merge_final" })
        {
            var sut = BuildSut(tracker);
            var result = await sut.HandoffListHandler(handlerTask.Id, survivors, phase, CancellationToken.None);
            Assert.False(result.Coerced);
            Assert.Equal(phase, result.NextPhase);
        }
    }

    [Fact]
    public async Task HandoffListHandler_PastCap_CoercesNonFinalPhaseToItsFinalCounterpart()
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId);
        var survivor = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor");
        var tracker = new HandoffRoundTracker();
        var survivors = new[] { survivor.Id };

        foreach (var phase in new[] { "wait", "merge", "wait", "merge" })
        {
            var sut = BuildSut(tracker);
            var result = await sut.HandoffListHandler(handlerTask.Id, survivors, phase, CancellationToken.None);
            Assert.False(result.Coerced);
        }

        var fifthSut = BuildSut(tracker);
        var fifth = await fifthSut.HandoffListHandler(handlerTask.Id, survivors, "wait", CancellationToken.None);

        Assert.True(fifth.Coerced);
        Assert.Equal("wait_final", fifth.NextPhase);
    }

    [Fact]
    public async Task HandoffListHandler_AfterMergeFinal_AnyFurtherHandoffThrows()
    {
        var listId = await SeedListAsync();
        var handlerTask = await SeedTaskAsync(listId);
        var survivor = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor");
        var tracker = new HandoffRoundTracker();
        var survivors = new[] { survivor.Id };

        var firstSut = BuildSut(tracker);
        await firstSut.HandoffListHandler(handlerTask.Id, survivors, "merge_final", CancellationToken.None);

        var secondSut = BuildSut(tracker);
        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            secondSut.HandoffListHandler(handlerTask.Id, survivors, "wait", CancellationToken.None));
    }

    [Fact]
    public async Task HandoffListHandler_TwoHandlerTasks_DoNotShareACounter()
    {
        var listId = await SeedListAsync();
        var handlerA = await SeedTaskAsync(listId, title: "Handler A");
        var handlerB = await SeedTaskAsync(listId, title: "Handler B");
        var survivorA = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor A");
        var survivorB = await SeedTaskAsync(listId, TaskStatus.WaitingForReview, title: "Survivor B");
        var tracker = new HandoffRoundTracker();

        foreach (var phase in new[] { "wait", "merge", "wait", "merge" })
        {
            var sut = BuildSut(tracker);
            await sut.HandoffListHandler(handlerA.Id, new[] { survivorA.Id }, phase, CancellationToken.None);
        }

        var sutB = BuildSut(tracker);
        var resultB = await sutB.HandoffListHandler(handlerB.Id, new[] { survivorB.Id }, "wait", CancellationToken.None);

        Assert.False(resultB.Coerced);
        Assert.Equal("wait", resultB.NextPhase);
    }
}
