using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Tests.Infrastructure;
using Microsoft.AspNetCore.SignalR;
using Xunit;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Worker.Tests.Hub;

public sealed class WorktreeStateHubTests : IDisposable
{
    private readonly DbFixture _db = new();

    public void Dispose() => _db.Dispose();

    private WorkerHub CreateHub()
    {
        var broadcaster = new HubBroadcaster(new CapturingHubContext());
        var hub = new WorkerHub(
            null!, null!, null!, null!, broadcaster, _db.CreateFactory(),
            null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!, null!,
            null!, new ClaudeDo.Worker.Online.OnlineInboxConfig(), new ClaudeDo.Worker.OnlineRefreshTokenStore(new ClaudeDo.Worker.DpapiTokenStore("unused.token")),
            new ClaudeDo.Worker.Runner.PendingQuestionRegistry(), null!);
        hub.Clients = new FakeHubCallerClients(new RecordingClientProxy());
        hub.Context = new FakeHubCallerContext();
        return hub;
    }

    private async Task<string> SeedWorktreeAsync(WorktreeState initial)
    {
        using var ctx = _db.CreateContext();
        var listId = Guid.NewGuid().ToString();
        var taskId = Guid.NewGuid().ToString();
        await new ListRepository(ctx).AddAsync(new ListEntity
        {
            Id = listId, Name = "L", CreatedAt = DateTime.UtcNow,
        });
        await new TaskRepository(ctx).AddAsync(new TaskEntity
        {
            Id = taskId, ListId = listId, Title = "T",
            Status = TaskStatus.Done, CreatedAt = DateTime.UtcNow, CommitType = "feat",
        });
        await new WorktreeRepository(ctx).AddAsync(new WorktreeEntity
        {
            TaskId = taskId, Path = "/tmp/x", BranchName = "claudedo/x",
            BaseCommit = "deadbeef", State = initial, CreatedAt = DateTime.UtcNow,
        });
        return taskId;
    }

    [Fact]
    public async Task SetWorktreeState_Active_To_Discarded_Succeeds()
    {
        var taskId = await SeedWorktreeAsync(WorktreeState.Active);
        var hub = CreateHub();

        var ok = await hub.SetWorktreeState(taskId, WorktreeState.Discarded);

        Assert.True(ok);
        using var ctx = _db.CreateContext();
        var row = await new WorktreeRepository(ctx).GetByTaskIdAsync(taskId);
        Assert.Equal(WorktreeState.Discarded, row!.State);
    }

    [Fact]
    public async Task SetWorktreeState_Merged_To_Active_Throws()
    {
        var taskId = await SeedWorktreeAsync(WorktreeState.Merged);
        var hub = CreateHub();

        await Assert.ThrowsAsync<HubException>(() =>
            hub.SetWorktreeState(taskId, WorktreeState.Active));

        using var ctx = _db.CreateContext();
        var row = await new WorktreeRepository(ctx).GetByTaskIdAsync(taskId);
        Assert.Equal(WorktreeState.Merged, row!.State);
    }

    [Fact]
    public async Task SetWorktreeState_Discarded_To_Kept_Throws()
    {
        var taskId = await SeedWorktreeAsync(WorktreeState.Discarded);
        var hub = CreateHub();

        await Assert.ThrowsAsync<HubException>(() =>
            hub.SetWorktreeState(taskId, WorktreeState.Kept));
    }

    [Fact]
    public async Task SetWorktreeState_SameState_IsNoOp()
    {
        var taskId = await SeedWorktreeAsync(WorktreeState.Active);
        var hub = CreateHub();

        var ok = await hub.SetWorktreeState(taskId, WorktreeState.Active);

        Assert.True(ok);
    }

    [Fact]
    public async Task SetWorktreeState_Missing_Throws()
    {
        var hub = CreateHub();

        await Assert.ThrowsAsync<HubException>(() =>
            hub.SetWorktreeState("does-not-exist", WorktreeState.Discarded));
    }
}
