using ClaudeDo.Data;
using ClaudeDo.Data.Git;
using ClaudeDo.Data.Models;
using Microsoft.EntityFrameworkCore;

namespace ClaudeDo.Worker.Services;

public sealed class WorktreeMaintenanceService
{
    public sealed record CleanupResult(int Removed);
    public sealed record ResetResult(int Removed, int TasksAffected, bool Blocked, int RunningTasks);

    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
    private readonly GitService _git;
    private readonly ILogger<WorktreeMaintenanceService> _logger;

    public WorktreeMaintenanceService(
        IDbContextFactory<ClaudeDoDbContext> dbFactory,
        GitService git,
        ILogger<WorktreeMaintenanceService> logger)
    {
        _dbFactory = dbFactory;
        _git = git;
        _logger = logger;
    }

    public async Task<CleanupResult> CleanupFinishedAsync(CancellationToken ct = default)
    {
        using var context = _dbFactory.CreateDbContext();
        var rows = await (from w in context.Worktrees
                          join t in context.Tasks on w.TaskId equals t.Id
                          join l in context.Lists on t.ListId equals l.Id
                          where w.State == WorktreeState.Merged || w.State == WorktreeState.Discarded
                          select new WorktreeRow(w.TaskId, w.Path, w.BranchName, l.WorkingDir))
                         .AsNoTracking()
                         .ToListAsync(ct);

        int removed = 0;
        foreach (var row in rows)
        {
            if (await TryRemoveAsync(row, force: false, ct))
                removed++;
        }
        return new CleanupResult(removed);
    }

    public async Task<ResetResult> ResetAllAsync(CancellationToken ct = default)
    {
        using var context = _dbFactory.CreateDbContext();
        var running = await context.Tasks.AsNoTracking()
            .CountAsync(t => t.Status == ClaudeDo.Data.Models.TaskStatus.Running, ct);
        if (running > 0)
            return new ResetResult(0, 0, Blocked: true, RunningTasks: running);

        var rows = await (from w in context.Worktrees
                          join t in context.Tasks on w.TaskId equals t.Id
                          join l in context.Lists on t.ListId equals l.Id
                          select new WorktreeRow(w.TaskId, w.Path, w.BranchName, l.WorkingDir))
                         .AsNoTracking()
                         .ToListAsync(ct);

        int removed = 0;
        foreach (var row in rows)
        {
            if (await TryRemoveAsync(row, force: true, ct))
                removed++;
        }
        return new ResetResult(removed, rows.Count, Blocked: false, RunningTasks: 0);
    }

    private async Task<bool> TryRemoveAsync(WorktreeRow row, bool force, CancellationToken ct)
    {
        var repoDirExists = !string.IsNullOrWhiteSpace(row.WorkingDir) && Directory.Exists(row.WorkingDir);

        if (repoDirExists)
        {
            try
            {
                await _git.WorktreeRemoveAsync(row.WorkingDir!, row.Path, force, ct);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex,
                    "git worktree remove failed for {Path}; falling back to directory delete", row.Path);
                try { if (Directory.Exists(row.Path)) Directory.Delete(row.Path, recursive: true); }
                catch (Exception delEx)
                {
                    _logger.LogError(delEx, "Directory.Delete fallback also failed for {Path}", row.Path);
                }
            }
        }
        else
        {
            try { if (Directory.Exists(row.Path)) Directory.Delete(row.Path, recursive: true); }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Directory.Delete failed for {Path}", row.Path);
            }
        }

        // Branch cleanup: otherwise rerunning the task hits "branch already exists".
        // Prune first so git no longer thinks the branch is checked out by a phantom worktree.
        if (repoDirExists)
        {
            try { await _git.WorktreePruneAsync(row.WorkingDir!, ct); }
            catch (Exception ex) { _logger.LogWarning(ex, "git worktree prune failed for {Repo}", row.WorkingDir); }

            if (!string.IsNullOrWhiteSpace(row.BranchName))
            {
                try
                {
                    await _git.BranchDeleteAsync(row.WorkingDir!, row.BranchName, force: true, ct);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to delete branch {Branch} for worktree {Path}",
                        row.BranchName, row.Path);
                }
            }
        }

        using var context = _dbFactory.CreateDbContext();
        await context.Worktrees.Where(w => w.TaskId == row.TaskId).ExecuteDeleteAsync(ct);
        return true;
    }

    private sealed record WorktreeRow(string TaskId, string Path, string BranchName, string? WorkingDir);
}
