using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using Microsoft.EntityFrameworkCore;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Worker.Queue;

public sealed class QueuePicker : IQueuePicker
{
    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;

    public QueuePicker(IDbContextFactory<ClaudeDoDbContext> dbFactory)
        => _dbFactory = dbFactory;

    public async Task<TaskEntity?> ClaimNextAsync(DateTime now, CancellationToken ct)
    {
        await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
        var nowStr = now.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.fffffff");
        var startedAtStr = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fffffff");

        // Atomic queue claim: UPDATE + RETURNING in a single statement prevents TOCTOU races.
        // Raw SQL because EF cannot express UPDATE...RETURNING.
        // Eligible task must be Queued, unblocked (chain and depends-on), not manual, and due
        // (or unscheduled). depends_on_task_id only gates on the dependency's Status='done' --
        // unlike blocked_by_task_id there is no cascade on the dependency failing, so a task
        // with a Failed dependency simply stays here, skipped, until someone intervenes.
        // EF SQLite stores DateTime as "yyyy-MM-dd HH:mm:ss.fffffff" — same format used here for comparison.
        var rows = await ctx.Tasks.FromSqlRaw("""
            UPDATE tasks SET status = 'running', started_at = {1}
            WHERE id = (
                SELECT t.id FROM tasks t
                WHERE t.status = 'queued'
                  AND t.blocked_by_task_id IS NULL
                  AND t.is_manual = 0
                  AND (t.scheduled_for IS NULL OR t.scheduled_for <= {0})
                  AND (t.depends_on_task_id IS NULL
                       OR EXISTS (
                           SELECT 1 FROM tasks d
                           WHERE d.id = t.depends_on_task_id AND d.status = 'done'
                       ))
                ORDER BY t.sort_order ASC, t.created_at ASC
                LIMIT 1
            )
            RETURNING *
            """, nowStr, startedAtStr).ToListAsync(ct);

        return rows.FirstOrDefault();
    }
}
