using System.Text.Json;
using Health.Application.Diets;
using Health.Infrastructure.Data.Records;

namespace Health.Infrastructure.Diets;

public sealed class DietImageAnalysisQueue(AppDbContext db) : IDietImageAnalysisQueue
{
    private const int MaxAttempts = 3;
    private readonly AppDbContext _db = db;

    public async Task<Guid> EnqueueAsync(IReadOnlyList<string> filePaths, CancellationToken ct)
    {
        var now = DateTime.UtcNow;
        var task = new DietImageAnalysisTaskRecord
        {
            Id = Guid.NewGuid(),
            FilePaths = JsonSerializer.Serialize(filePaths),
            AvailableAt = now,
            CreatedAt = now,
            UpdatedAt = now,
        };
        await _db.DietImageAnalysisTasks.AddAsync(task, ct);
        await _db.SaveChangesAsync(ct);
        return task.Id;
    }

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

        // 超时但仍有重试余量 → 重新排队
        await _db.DietImageAnalysisTasks
            .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（也让 WaitForResultAsync 能尽快返回失败）
        await _db.DietImageAnalysisTasks
            .Where(x => x.Status == "Processing" && x.UpdatedAt < staleCutoff && x.Attempts >= MaxAttempts)
            .ExecuteUpdateAsync(setters => setters
                .SetProperty(x => x.Status, "Failed")
                .SetProperty(x => x.ResultCode, 50001)
                .SetProperty(x => x.ResultMessage, "食物识别超时且已达最大重试次数")
                .SetProperty(x => x.LastError, "任务执行超时且已达最大重试次数")
                .SetProperty(x => x.UpdatedAt, now), ct);
    }

    public async Task<DietImageAnalysisJob?> TryTakeAsync(CancellationToken ct)
    {
        var now = DateTime.UtcNow;
        var candidate = await _db.DietImageAnalysisTasks.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.DietImageAnalysisTasks
            .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);
        if (claimed == 0) return null;

        var paths = JsonSerializer.Deserialize<List<string>>(candidate.FilePaths) ?? [];
        return new DietImageAnalysisJob(candidate.Id, paths);
    }

    public async Task<DietImageAnalysisResult> WaitForResultAsync(Guid jobId, CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            var task = await _db.DietImageAnalysisTasks.AsNoTracking()
                .FirstOrDefaultAsync(x => x.Id == jobId, ct);
            if (task == null)
                return new DietImageAnalysisResult(false, 40004, null, "饮食识别任务不存在");
            if (task.Status is "Completed" or "Failed")
                return new DietImageAnalysisResult(
                    task.Status == "Completed",
                    task.ResultCode ?? 50001,
                    task.ResultData,
                    task.ResultMessage);
            await Task.Delay(TimeSpan.FromMilliseconds(500), ct);
        }

        throw new OperationCanceledException(ct);
    }

    public Task CompleteAsync(Guid jobId, DietImageAnalysisResult result, CancellationToken ct)
    {
        var now = DateTime.UtcNow;
        return _db.DietImageAnalysisTasks.Where(x => x.Id == jobId)
            .ExecuteUpdateAsync(setters => setters
                .SetProperty(x => x.Status, result.Success ? "Completed" : "Failed")
                .SetProperty(x => x.ResultCode, result.Code)
                .SetProperty(x => x.ResultData, result.Data)
                .SetProperty(x => x.ResultMessage, result.Message)
                .SetProperty(x => x.UpdatedAt, now), ct);
    }

    public async Task<bool> RetryAsync(Guid jobId, string error, CancellationToken ct)
    {
        var task = await _db.DietImageAnalysisTasks.FirstOrDefaultAsync(x => x.Id == jobId, ct);
        if (task == null) return true;

        task.LastError = error.Length > 2000 ? error[..2000] : error;
        task.UpdatedAt = DateTime.UtcNow;
        if (task.Attempts >= MaxAttempts)
        {
            task.Status = "Failed";
            task.ResultCode = 50001;
            task.ResultMessage = $"食物识别失败: {error}";
        }
        else
        {
            task.Status = "Pending";
            task.AvailableAt = DateTime.UtcNow.AddSeconds(Math.Pow(2, task.Attempts) * 5);
        }
        await _db.SaveChangesAsync(ct);
        return task.Status == "Failed";
    }
}
