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

namespace Jiaowu.Api.Infrastructure.OfficialDocuments;

public sealed class OfficialDocumentService(
    AppDbContext db,
    OfficialDocumentOptions options,
    IOfficialDocumentPdfGenerator pdfGenerator)
{
    private static readonly JsonSerializerOptions SnapshotJsonOptions = new(JsonSerializerDefaults.Web);

    public async Task<OfficialDocument> CreateAsync(
        Guid studentId,
        OfficialDocumentType type,
        string? purpose,
        Guid issuedByUserId,
        string issuedByName,
        string verificationBaseUrl,
        Guid? reissuedFromDocumentId,
        CancellationToken cancellationToken)
    {
        var student = await db.Students.AsNoTracking()
            .Where(x => x.Id == studentId)
            .Select(x => new
            {
                x.StudentNumber,
                x.Name,
                x.Gender,
                x.EnrollmentYear,
                x.EnrollmentDate,
                x.Status,
                CollegeName = x.AdministrativeClass!.Major!.College!.Name,
                MajorName = x.AdministrativeClass.Major.Name,
                ClassName = x.AdministrativeClass.Name
            })
            .SingleOrDefaultAsync(cancellationToken)
            ?? throw new OfficialDocumentValidationException("学生档案不存在。");

        var grades = type == OfficialDocumentType.Transcript
            ? await db.GradeRecords.AsNoTracking()
                .Where(x =>
                    x.StudentId == studentId &&
                    x.GradeSheet!.Status == GradeSheetStatus.Published)
                .OrderBy(x => x.GradeSheet!.TeachingTask!.AcademicTerm!.StartDate)
                .ThenBy(x => x.GradeSheet!.TeachingTask!.Course!.Code)
                .Select(x => new OfficialTranscriptRow(
                    x.GradeSheet!.TeachingTask!.AcademicTerm!.Name,
                    x.GradeSheet.TeachingTask.Course!.Code,
                    x.GradeSheet.TeachingTask.Course.Name,
                    x.GradeSheet.TeachingTask.Course.Credits,
                    x.TotalScore,
                    x.GradePoint,
                    GradeExamStatusLabel(x.ExamStatus)))
                .ToListAsync(cancellationToken)
            : [];

        if (type == OfficialDocumentType.Transcript && grades.Count == 0)
            throw new OfficialDocumentValidationException("该学生暂无已发布成绩，不能签发官方成绩单。");

        var issuedAt = DateTime.UtcNow;
        var documentNumber = CreateDocumentNumber(type, issuedAt);
        var verificationCode = CreateVerificationCode();
        var studentSnapshot = new OfficialStudentSnapshot(
            student.StudentNumber,
            student.Name,
            GenderLabel(student.Gender),
            student.CollegeName,
            student.MajorName,
            student.ClassName,
            student.EnrollmentYear,
            student.EnrollmentDate,
            StudentStatusLabel(student.Status));
        var totalCredits = grades.Sum(x => x.Credits);
        var passedGrades = grades
            .Where(x => x.Score >= 60 || x.ExamStatus == "免修")
            .ToArray();
        var gpaGrades = grades.Where(x => x.GradePoint.HasValue).ToArray();
        var gpaCredits = gpaGrades.Sum(x => x.Credits);
        var gpa = gpaCredits > 0
            ? gpaGrades.Sum(x => x.GradePoint!.Value * x.Credits) / gpaCredits
            : (decimal?)null;
        var snapshot = new OfficialDocumentSnapshot(
            options.InstitutionName,
            options.IssuingOffice,
            documentNumber,
            type,
            issuedAt,
            issuedByName,
            string.IsNullOrWhiteSpace(purpose) ? null : purpose.Trim(),
            studentSnapshot,
            grades,
            totalCredits,
            passedGrades.Sum(x => x.Credits),
            gpa);
        var verificationUrl = $"{verificationBaseUrl.TrimEnd('/')}/verify/{verificationCode}";
        var generated = pdfGenerator.Generate(snapshot, verificationUrl);

        return new OfficialDocument
        {
            DocumentNumber = documentNumber,
            VerificationCodeHash = HashVerificationCode(verificationCode),
            Type = type,
            Status = OfficialDocumentStatus.Valid,
            StudentId = studentId,
            IssuedByUserId = issuedByUserId,
            IssuedAt = issuedAt,
            Purpose = snapshot.Purpose,
            SnapshotJson = JsonSerializer.Serialize(snapshot, SnapshotJsonOptions),
            PdfContent = generated.Content,
            PdfSha256 = generated.Sha256,
            ReissuedFromDocumentId = reissuedFromDocumentId
        };
    }

    public static string HashVerificationCode(string verificationCode) =>
        Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(verificationCode)))
            .ToLowerInvariant();

    public static OfficialDocumentSnapshot DeserializeSnapshot(string json) =>
        JsonSerializer.Deserialize<OfficialDocumentSnapshot>(json, SnapshotJsonOptions)
        ?? throw new InvalidOperationException("官方凭证快照无法读取。");

    private string CreateDocumentNumber(OfficialDocumentType type, DateTime issuedAt)
    {
        var typeCode = type == OfficialDocumentType.Transcript ? "TR" : "SC";
        var random = Convert.ToHexString(RandomNumberGenerator.GetBytes(6));
        return $"{options.DocumentNumberPrefix}-{typeCode}-{issuedAt:yyyyMMdd}-{random}";
    }

    private static string CreateVerificationCode() =>
        Convert.ToHexString(RandomNumberGenerator.GetBytes(24)).ToLowerInvariant();

    private static string GenderLabel(Gender value) => value switch
    {
        Gender.Male => "男",
        Gender.Female => "女",
        _ => "未登记"
    };

    private static string StudentStatusLabel(StudentStatus value) => value switch
    {
        StudentStatus.Active => "在读",
        StudentStatus.Suspended => "休学",
        StudentStatus.Graduated => "已毕业",
        StudentStatus.Withdrawn => "退学",
        _ => "未知"
    };

    private static string GradeExamStatusLabel(GradeExamStatus value) => value switch
    {
        GradeExamStatus.Normal => "正常",
        GradeExamStatus.Absent => "缺考",
        GradeExamStatus.Deferred => "缓考",
        GradeExamStatus.Exempt => "免修",
        GradeExamStatus.Makeup => "补考",
        _ => "未知"
    };
}

public sealed class OfficialDocumentValidationException(string message) : Exception(message);
