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

namespace ClaudeDo.Worker.Tests.Tickets;

/// TicketProjectId sits in the SetConfigAsync-copies-verbatim trap: any writer that doesn't carry
/// the field forward silently clears the list&lt;-&gt;ticket-project link. UpdateListConfigDto's
/// TicketProjectId is therefore tri-state: null = keep stored, &lt;=0 = clear, &gt;0 = set. The MCP
/// set_list_config tool doesn't expose the field at all (UI-only), so it must preserve whatever is
/// already stored.
public sealed class ListConfigTicketProjectTests : IDisposable
{
    private readonly DbFixture _db = new();

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

    private WorkerHub CreateHub()
    {
        var factory = _db.CreateFactory();
        var broadcaster = new HubBroadcaster(new CapturingHubContext());
        var hub = new WorkerHub(
            null!, null!, null!, null!, broadcaster, factory,
            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> SeedListAsync()
    {
        var listId = Guid.NewGuid().ToString();
        await using var ctx = _db.CreateContext();
        await new ListRepository(ctx).AddAsync(new ListEntity { Id = listId, Name = "L", CreatedAt = DateTime.UtcNow });
        return listId;
    }

    private async Task SeedConfigAsync(string listId, int? ticketProjectId = null, string? model = null)
    {
        await using var ctx = _db.CreateContext();
        await new ListRepository(ctx).SetConfigAsync(new ListConfigEntity
        {
            ListId = listId, Model = model, TicketProjectId = ticketProjectId,
        });
    }

    private async Task<ListConfigEntity?> ReadConfigAsync(string listId)
    {
        await using var ctx = _db.CreateContext();
        return await new ListRepository(ctx).GetConfigAsync(listId);
    }

    private async Task CallSetListConfigAsync(string listId, string model)
    {
        var factory = _db.CreateFactory();
        await using var ctx = factory.CreateDbContext();
        var tool = new ConfigMcpTools(
            new ListRepository(ctx), new TaskRepository(ctx),
            new HubBroadcaster(new CapturingHubContext()), factory);
        await tool.SetListConfig(listId, model: model, cancellationToken: CancellationToken.None);
    }

    [Fact]
    public async Task set_list_config_preserves_TicketProjectId()
    {
        var listId = await SeedListAsync();
        await SeedConfigAsync(listId, ticketProjectId: 393);

        await CallSetListConfigAsync(listId, model: "opus");

        var config = await ReadConfigAsync(listId);
        Assert.Equal(393, config!.TicketProjectId);
        Assert.Equal("opus", config.Model);
    }

    [Fact]
    public async Task UpdateListConfig_with_null_TicketProjectId_keeps_the_stored_value()
    {
        var hub = CreateHub();
        var listId = await SeedListAsync();
        await SeedConfigAsync(listId, ticketProjectId: 393);

        await hub.UpdateListConfig(new UpdateListConfigDto(listId, "opus", null, null));

        Assert.Equal(393, (await ReadConfigAsync(listId))!.TicketProjectId);
    }

    [Fact]
    public async Task UpdateListConfig_can_clear_the_link_explicitly()
    {
        var hub = CreateHub();
        var listId = await SeedListAsync();
        await SeedConfigAsync(listId, ticketProjectId: 393);

        await hub.UpdateListConfig(new UpdateListConfigDto(listId, "opus", null, null, TicketProjectId: 0));

        Assert.Null((await ReadConfigAsync(listId))!.TicketProjectId);
    }
}
