using ClaudeDo.Data;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Runner;
using Microsoft.EntityFrameworkCore;

namespace ClaudeDo.Worker.Queue;

public sealed class OverrideSlotService
{
    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
    private readonly TaskRunner _runner;
    private readonly ILogger<OverrideSlotService> _logger;

    private readonly object _lock = new();
    private volatile QueueSlotState? _slot;

    public OverrideSlotService(
        IDbContextFactory<ClaudeDoDbContext> dbFactory,
        TaskRunner runner,
        ILogger<OverrideSlotService> logger)
    {
        _dbFactory = dbFactory;
        _runner = runner;
        _logger = logger;
    }

    public QueueSlotState? CurrentSlot => _slot;

    public async Task RunNow(string taskId)
    {
        using (var context = _dbFactory.CreateDbContext())
        {
            var taskRepo = new TaskRepository(context);
            var exists = await taskRepo.GetByIdAsync(taskId);
            if (exists is null)
                throw new KeyNotFoundException($"Task '{taskId}' not found.");
        }

        lock (_lock)
        {
            if (_slot is not null)
                throw new InvalidOperationException("override slot busy");

            var cts = new CancellationTokenSource();
            _slot = new QueueSlotState { TaskId = taskId, StartedAt = DateTime.UtcNow, Cts = cts };

            _ = RunInSlotAsync(taskId, cts.Token).ContinueWith(t =>
            {
                if (t.IsFaulted)
                    _logger.LogError(t.Exception, "RunInSlotAsync failed for task {TaskId}", taskId);
                lock (_lock) { _slot = null; }
                cts.Dispose();
            }, TaskScheduler.Default);
        }
    }

    public async Task<string> ContinueTask(string taskId, string followUpPrompt)
    {
        using var context = _dbFactory.CreateDbContext();
        var taskRepo = new TaskRepository(context);
        var task = await taskRepo.GetByIdAsync(taskId)
            ?? throw new KeyNotFoundException($"Task '{taskId}' not found.");

        if (task.Status == Data.Models.TaskStatus.Running)
            throw new InvalidOperationException("task is already running");

        lock (_lock)
        {
            if (_slot is not null)
                throw new InvalidOperationException("override slot busy");

            var cts = new CancellationTokenSource();
            _slot = new QueueSlotState { TaskId = taskId, StartedAt = DateTime.UtcNow, Cts = cts };

            _ = RunContinueInSlotAsync(taskId, followUpPrompt, cts.Token).ContinueWith(t =>
            {
                if (t.IsFaulted)
                    _logger.LogError(t.Exception, "RunContinueInSlotAsync failed for task {TaskId}", taskId);
                lock (_lock) { _slot = null; }
                cts.Dispose();
            }, TaskScheduler.Default);
        }

        return taskId;
    }

    public bool TryCancel(string taskId)
    {
        lock (_lock)
        {
            if (_slot is not null && _slot.TaskId == taskId)
            {
                _slot.Cts.Cancel();
                return true;
            }
        }
        return false;
    }

    private async Task RunInSlotAsync(string taskId, CancellationToken ct)
    {
        try
        {
            _logger.LogInformation("Starting task {TaskId} in override slot", taskId);

            Data.Models.TaskEntity task;
            using (var context = _dbFactory.CreateDbContext())
            {
                var taskRepo = new TaskRepository(context);
                task = await taskRepo.GetByIdAsync(taskId, ct)
                    ?? throw new KeyNotFoundException($"Task '{taskId}' not found.");
            }

            await _runner.RunAsync(task, "override", ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Override slot runner error for task {TaskId}", taskId);
        }
    }

    private async Task RunContinueInSlotAsync(string taskId, string followUpPrompt, CancellationToken ct)
    {
        try
        {
            _logger.LogInformation("Continuing task {TaskId} in override slot", taskId);
            await _runner.ContinueAsync(taskId, followUpPrompt, "override", ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Continue runner error for task {TaskId}", taskId);
        }
    }
}
