using ClaudeDo.Data;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Runner;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Worker.Refine;

public sealed record RefineRunOutcome(bool Success, string Message);

public sealed class RefineRunner
{
    private static readonly TimeSpan RunTimeout = TimeSpan.FromMinutes(5);
    private const int MaxTurns = 5;

    private readonly IClaudeProcess _claude;
    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
    private readonly ILogger<RefineRunner> _logger;
    private readonly IRefineBroadcaster _broadcaster;

    private readonly object _lock = new();
    private readonly HashSet<string> _inFlight = new();

    public RefineRunner(
        IClaudeProcess claude,
        IDbContextFactory<ClaudeDoDbContext> dbFactory,
        ILogger<RefineRunner> logger,
        IRefineBroadcaster broadcaster)
    {
        _claude = claude;
        _dbFactory = dbFactory;
        _logger = logger;
        _broadcaster = broadcaster;
    }

    public async Task<RefineRunOutcome> RefineAsync(string taskId, CancellationToken ct)
    {
        lock (_lock)
        {
            if (!_inFlight.Add(taskId))
                return new RefineRunOutcome(false, "Already refining this task");
        }

        var success = false;
        string? error = null;
        try
        {
            ClaudeDo.Data.Models.TaskEntity task;
            List<ClaudeDo.Data.Models.SubtaskEntity> subs;
            string? workingDir;
            await using (var dbCtx = await _dbFactory.CreateDbContextAsync(ct))
            {
                var tasks = new TaskRepository(dbCtx);
                task = await tasks.GetByIdAsync(taskId, ct)
                    ?? throw new InvalidOperationException($"Task {taskId} not found.");
                if (task.Status != TaskStatus.Idle)
                    return new RefineRunOutcome(false, $"Task must be Idle to refine (is {task.Status}).");
                subs = await new SubtaskRepository(dbCtx).GetByTaskIdAsync(taskId, ct);
                var list = await new ListRepository(dbCtx).GetByIdAsync(task.ListId, ct);
                workingDir = list?.WorkingDir;
            }

            var canReadRepo = !string.IsNullOrWhiteSpace(workingDir) && Directory.Exists(workingDir);
            var cwd = canReadRepo ? workingDir! : Paths.AppDataRoot();
            Directory.CreateDirectory(cwd);

            var logPath = RefinePrompt.LogPath(taskId);
            try { if (File.Exists(logPath)) File.Delete(logPath); } catch { }
            await using var logWriter = new LogWriter(logPath);

            await _broadcaster.RefineStartedAsync(taskId);

            var prompt = RefinePrompt.BuildPrompt(task, subs);
            var args = RefinePrompt.BuildArgs(MaxTurns, canReadRepo);

            using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
            timeoutCts.CancelAfter(RunTimeout);

            var result = await _claude.RunAsync(
                arguments: args,
                prompt: prompt,
                workingDirectory: cwd,
                onStdoutLine: async line => await logWriter.WriteLineAsync(line),
                ct: timeoutCts.Token);

            success = result.IsSuccess;
            if (!success) error = $"exit code {result.ExitCode}";
            return success
                ? new RefineRunOutcome(true, "Refine complete")
                : new RefineRunOutcome(false, error!);
        }
        catch (OperationCanceledException) when (!ct.IsCancellationRequested)
        {
            error = $"timed out after {RunTimeout.TotalMinutes:0} min";
            return new RefineRunOutcome(false, error);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Refine run failed for {TaskId}", taskId);
            error = ex.Message;
            return new RefineRunOutcome(false, ex.Message);
        }
        finally
        {
            await _broadcaster.RefineFinishedAsync(taskId, success, error);
            lock (_lock) { _inFlight.Remove(taskId); }
        }
    }
}
