using ClaudeDo.Data;
using ClaudeDo.Worker.Config;
using ClaudeDo.Worker.Git;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Planning;
using ClaudeDo.Worker.Queue;
using ClaudeDo.Worker.State;
using ClaudeDo.Worker.Tickets;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;

namespace ClaudeDo.Worker.Tests.Infrastructure;

/// Test-only helper that wires TaskStateService and PlanningChainCoordinator
/// against a shared DB factory, breaking the Func cycle between them.
public static class TaskStateServiceBuilder
{
    public sealed record Built(
        TaskStateService State,
        PlanningChainCoordinator Chain,
        CapturingHubContext Hub,
        Func<int> WakeCount,
        CountingQueueWaker Waker,
        RunCancellationRegistry RunCancels);

    public static Built Build(
        IDbContextFactory<ClaudeDoDbContext> dbFactory,
        Func<IActiveMergeState>? mergeState = null,
        BaseDirtyChecker? baseDirtyChecker = null)
    {
        var hub = new CapturingHubContext();
        var broadcaster = new HubBroadcaster(hub);
        var waker = new CountingQueueWaker();
        var runCancels = new RunCancellationRegistry(NullLogger<RunCancellationRegistry>.Instance);

        // Unconfigured on purpose: no base URL, no PAT file at this (unused) path — IsConfigured
        // is false, so TicketStatusSync.SyncAsync short-circuits before touching the DB or the
        // network. Tests that care about ticket sync build TicketStatusSync themselves.
        var ticketConfig = new TicketSystemConfig(
            new WorkerConfig(),
            new TicketPatStore(new DpapiTokenStore(Path.Combine(Path.GetTempPath(), $"cdo-unused-pat-{Guid.NewGuid():N}"))));
        var ticketSync = new TicketStatusSync(
            ticketConfig,
            new TicketClientFactory(ticketConfig, new NoopHttpClientFactory(), NullLogger<BandelTicketClient>.Instance),
            dbFactory,
            NullLogger<TicketStatusSync>.Instance);

        TaskStateService? state = null;
        var chain = new PlanningChainCoordinator(dbFactory, () => state!);
        state = new TaskStateService(
            dbFactory,
            broadcaster,
            waker,
            chain,
            runCancels,
            mergeState ?? (() => NoActiveMergeState.Instance),
            baseDirtyChecker ?? new BaseDirtyChecker(new ClaudeDo.Data.Git.GitService(), NullLogger<BaseDirtyChecker>.Instance),
            NullLogger<TaskStateService>.Instance,
            ticketSync);

        return new Built(state, chain, hub, () => waker.Count, waker, runCancels);
    }
}

file sealed class NoActiveMergeState : IActiveMergeState
{
    public static readonly NoActiveMergeState Instance = new();
    public bool HasActiveMerge(string taskId) => false;
    public Task<bool> HasActiveMergeAsync(string taskId, CancellationToken ct) => Task.FromResult(false);
}

// Never actually invoked: TicketSystemConfig.IsConfigured is false in every TaskStateServiceBuilder
// test, so TicketClientFactory.Create() returns null before this factory would be called.
file sealed class NoopHttpClientFactory : IHttpClientFactory
{
    public HttpClient CreateClient(string name) => throw new InvalidOperationException(
        "Unexpected: ticket sync is not configured in tests built via TaskStateServiceBuilder.");
}

public sealed class CountingQueueWaker : IQueueWaker
{
    private int _count;
    public int Count => Volatile.Read(ref _count);
    public void Wake() => Interlocked.Increment(ref _count);
}
