using System.Text.Json;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Infrastructure.Exams;

public sealed class ExamArrangementJobProcessor(
    AppDbContext db,
    ExamArrangementService examArrangementService,
    MakeupExamArrangementService makeupExamArrangementService,
    IAppCache cache,
    ILogger<ExamArrangementJobProcessor> logger)
{
    public async Task ProcessAsync(Guid jobId, CancellationToken stoppingToken)
    {
        try
        {
            var job = await db.ExamArrangementJobs
                .FirstOrDefaultAsync(x => x.Id == jobId, stoppingToken);
            if (job is null ||
                job.Status is ExamArrangementJobStatus.Succeeded
                    or ExamArrangementJobStatus.Failed)
            {
                return;
            }

            job.Status = ExamArrangementJobStatus.Running;
            job.StartedAt ??= DateTime.UtcNow;
            job.CompletedAt = null;
            job.ErrorMessage = null;
            job.ResultMessage = null;
            job.CurrentStep = job.Kind == ExamArrangementKind.FormalExam
                ? "正在编排正式考试"
                : "正在编排补考";
            await db.SaveChangesAsync(stoppingToken);

            var sessionIds = DeserializeSessionIds(job.SessionIdsJson);
            var result = job.Kind switch
            {
                ExamArrangementKind.FormalExam =>
                    await examArrangementService.ArrangeAsync(
                        job.PlanId,
                        sessionIds,
                        job.AssignClassrooms,
                        job.AssignInvigilators,
                        stoppingToken),
                ExamArrangementKind.MakeupExam =>
                    await makeupExamArrangementService.ArrangeAsync(
                        job.PlanId,
                        sessionIds,
                        job.AssignClassrooms,
                        job.AssignInvigilators,
                        stoppingToken),
                _ => throw new InvalidOperationException(
                    $"不支持的考试编排类型：{job.Kind}。")
            };

            if (!result.Success)
            {
                await MarkFailedAsync(jobId, result.Message);
                return;
            }

            job.Status = ExamArrangementJobStatus.Succeeded;
            job.ProcessedSessions = job.TotalSessions;
            job.CurrentStep = "编排完成";
            job.ResultMessage = result.Message;
            job.ActivePlanId = null;
            job.CompletedAt = DateTime.UtcNow;
            await db.SaveChangesAsync(stoppingToken);

            if (job.Kind == ExamArrangementKind.FormalExam)
            {
                await cache.RemoveByTagAsync(
                    AppCacheTags.Timetables,
                    stoppingToken);
            }

            logger.LogInformation(
                "Exam arrangement job {JobId} for {Kind}/{PlanId} completed.",
                job.Id,
                job.Kind,
                job.PlanId);
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation(
                "Exam arrangement job {JobId} was interrupted by application shutdown.",
                jobId);
            throw;
        }
        catch (Exception exception)
        {
            logger.LogError(exception, "Exam arrangement job {JobId} failed.", jobId);
            await MarkFailedAsync(jobId, exception.GetBaseException().Message);
        }
    }

    private static IReadOnlyCollection<Guid>? DeserializeSessionIds(string? json)
    {
        if (string.IsNullOrWhiteSpace(json))
            return null;

        var sessionIds = JsonSerializer.Deserialize<Guid[]>(json);
        return sessionIds is { Length: > 0 } ? sessionIds : null;
    }

    private async Task MarkFailedAsync(Guid jobId, string message)
    {
        db.ChangeTracker.Clear();
        var job = await db.ExamArrangementJobs.FirstOrDefaultAsync(
            x => x.Id == jobId,
            CancellationToken.None);
        if (job is null)
            return;

        job.Status = ExamArrangementJobStatus.Failed;
        job.ActivePlanId = null;
        job.CurrentStep = "编排失败";
        job.ErrorMessage = message.Length <= 2000 ? message : message[..2000];
        job.CompletedAt = DateTime.UtcNow;
        await db.SaveChangesAsync(CancellationToken.None);
    }
}
