using Health.Application.Reports;
using Health.Application.Notifications;
using Health.Infrastructure.Data.Records;

namespace Health.Infrastructure.Reports;

public sealed class EfReportAnalysisQueue(
    AppDbContext db,
    IUserNotificationProducer? notifications = null) : IReportAnalysisQueue
{
    private const int MaxAttempts = 3;
    private readonly AppDbContext _db = db;
    private readonly IUserNotificationProducer? _notifications = notifications;

    public async Task EnqueueAsync(ReportAnalysisJob job, CancellationToken ct = default)
    {
        var now = DateTime.UtcNow;
        await _db.ReportAnalysisTasks.AddAsync(new ReportAnalysisTaskRecord
        {
            Id = job.TaskId,
            ReportId = job.ReportId,
            FilePath = job.FilePath,
            Status = "Pending",
            Attempts = 0,
            AvailableAt = now,
            CreatedAt = now,
            UpdatedAt = now,
        }, ct);
        await _db.SaveChangesAsync(ct);
    }

    public async Task RecoverStaleAsync(CancellationToken ct)
    {
        var now = DateTime.UtcNow;
        var staleCutoff = now.AddMinutes(-5);

        // 超时但仍有重试余量 → 重新排队
        await _db.ReportAnalysisTasks
            .Where(x => x.Status == "Processing" && x.UpdatedAt < staleCutoff && x.Attempts < MaxAttempts)
            .ExecuteUpdateAsync(setters => setters
                .SetProperty(x => x.Status, "Pending")
                .SetProperty(x => x.AvailableAt, now)
                .SetProperty(x => x.UpdatedAt, now), ct);

        // 超时且已达重试上限（多为最后一次执行中进程崩溃）→ 标记失败，避免永久卡在 Processing
        await _db.ReportAnalysisTasks
            .Where(x => x.Status == "Processing" && x.UpdatedAt < staleCutoff && x.Attempts >= MaxAttempts)
            .ExecuteUpdateAsync(setters => setters
                .SetProperty(x => x.Status, "Failed")
                .SetProperty(x => x.LastError, "任务执行超时且已达最大重试次数")
                .SetProperty(x => x.UpdatedAt, now), ct);
    }

    public async Task<ReportAnalysisJob?> TryTakeAsync(CancellationToken ct)
    {
        var now = DateTime.UtcNow;
        var candidate = await _db.ReportAnalysisTasks.AsNoTracking()
            .Where(x => x.Status == "Pending" && x.AvailableAt <= now && x.Attempts < MaxAttempts)
            .OrderBy(x => x.CreatedAt)
            .FirstOrDefaultAsync(ct);
        if (candidate == null) return null;

        var claimed = await _db.ReportAnalysisTasks
            .Where(x => x.Id == candidate.Id && x.Status == "Pending")
            .ExecuteUpdateAsync(setters => setters
                .SetProperty(x => x.Status, "Processing")
                .SetProperty(x => x.Attempts, x => x.Attempts + 1)
                .SetProperty(x => x.UpdatedAt, now), ct);
        return claimed == 0
            ? null
            : new ReportAnalysisJob(candidate.Id, candidate.ReportId, candidate.FilePath);
    }

    public async Task CompleteAsync(Guid taskId, CancellationToken ct)
    {
        await _db.ReportAnalysisTasks
            .Where(x => x.Id == taskId && x.Status == "Processing")
            .ExecuteUpdateAsync(setters => setters
                .SetProperty(x => x.Status, "Completed")
                .SetProperty(x => x.UpdatedAt, DateTime.UtcNow), ct);
    }

    public async Task RetryAsync(Guid taskId, string error, CancellationToken ct)
    {
        var task = await _db.ReportAnalysisTasks.FirstOrDefaultAsync(x => x.Id == taskId, ct);
        if (task == null) return;

        task.LastError = error.Length > 2000 ? error[..2000] : error;
        task.UpdatedAt = DateTime.UtcNow;
        if (task.Attempts >= MaxAttempts)
        {
            task.Status = "Failed";
        }
        else
        {
            task.Status = "Pending";
            task.AvailableAt = DateTime.UtcNow.AddSeconds(Math.Pow(2, task.Attempts) * 5);
        }
        await _db.SaveChangesAsync(ct);

        if (task.Status == "Failed" && _notifications != null)
        {
            var report = await _db.Reports.AsNoTracking()
                .Where(x => x.Id == task.ReportId)
                .Select(x => new { x.Id, x.UserId })
                .FirstOrDefaultAsync(ct);
            if (report != null)
            {
                await _notifications.EnqueueAsync(
                    report.UserId,
                    task.Id,
                    new NotificationMessage(
                        "ReportAnalysisFailed",
                        "报告分析未完成",
                        "这份报告暂时无法完成分析，您可以进入报告页面重新尝试。",
                        "warning",
                        "report",
                        report.Id.ToString()),
                    ct);
            }
        }
    }
}
