using System.Security.Cryptography;
using QRCoder;
using SkiaSharp;
using Jiaowu.Api.Infrastructure.Pdf;

namespace Jiaowu.Api.Infrastructure.OfficialDocuments;

public interface IOfficialDocumentPdfGenerator
{
    GeneratedOfficialDocument Generate(
        OfficialDocumentSnapshot snapshot,
        string verificationUrl);
}

public sealed class OfficialDocumentPdfGenerator(
    OfficialDocumentOptions options) : IOfficialDocumentPdfGenerator
{
    private const float PageWidth = 595;
    private const float PageHeight = 842;
    private const float Margin = 44;
    private const float ContentWidth = PageWidth - Margin * 2;
    private static readonly SKColor Ink = new(31, 48, 65);
    private static readonly SKColor Muted = new(91, 107, 120);
    private static readonly SKColor Accent = new(26, 91, 82);
    private static readonly SKColor Pale = new(236, 244, 242);
    private static readonly SKColor Rule = new(208, 219, 220);

    public GeneratedOfficialDocument Generate(
        OfficialDocumentSnapshot snapshot,
        string verificationUrl)
    {
        using var typeface = PdfTypefaceResolver.ResolveChineseTypeface(options.FontPath);
        using var stream = new MemoryStream();
        using (var document = SKDocument.CreatePdf(stream))
        {
            if (snapshot.Type == Domain.Academic.OfficialDocumentType.Transcript)
                DrawTranscript(document, typeface, snapshot, verificationUrl);
            else
                DrawStatusCertificate(document, typeface, snapshot, verificationUrl);
            document.Close();
        }

        var content = stream.ToArray();
        var hash = Convert.ToHexString(SHA256.HashData(content)).ToLowerInvariant();
        return new GeneratedOfficialDocument(content, hash);
    }

    private void DrawTranscript(
        SKDocument document,
        SKTypeface typeface,
        OfficialDocumentSnapshot snapshot,
        string verificationUrl)
    {
        const int rowsPerPage = 19;
        var pageCount = Math.Max(1, (int)Math.Ceiling(snapshot.Grades.Count / (double)rowsPerPage));
        for (var pageIndex = 0; pageIndex < pageCount; pageIndex++)
        {
            using var canvas = document.BeginPage(PageWidth, PageHeight);
            DrawPageFrame(canvas);
            DrawHeader(canvas, typeface, snapshot, "官方电子成绩单", "OFFICIAL ACADEMIC TRANSCRIPT");

            var y = 145f;
            if (pageIndex == 0)
            {
                y = DrawStudentSummary(canvas, typeface, snapshot.Student, y);
                y = DrawTranscriptSummary(canvas, typeface, snapshot, y + 12);
            }
            else
            {
                DrawText(canvas, typeface, $"{snapshot.Student.Name} · {snapshot.Student.StudentNumber}",
                    Margin, y, 10, Muted);
                y += 20;
            }

            var rows = snapshot.Grades.Skip(pageIndex * rowsPerPage).Take(rowsPerPage).ToArray();
            y = DrawGradeTable(canvas, typeface, rows, y + 14);

            if (pageIndex == pageCount - 1)
                DrawVerificationBlock(canvas, typeface, snapshot, verificationUrl, Math.Min(y + 18, 685));

            DrawFooter(canvas, typeface, snapshot, pageIndex + 1, pageCount);
            document.EndPage();
        }
    }

    private void DrawStatusCertificate(
        SKDocument document,
        SKTypeface typeface,
        OfficialDocumentSnapshot snapshot,
        string verificationUrl)
    {
        using var canvas = document.BeginPage(PageWidth, PageHeight);
        DrawPageFrame(canvas);
        DrawHeader(canvas, typeface, snapshot, "学籍状态证明", "CERTIFICATE OF STUDENT STATUS");

        var student = snapshot.Student;
        var statusText = student.Status switch
        {
            "在读" => "现为我校在读学生",
            "休学" => "现为我校注册学生，当前处于休学状态",
            "已毕业" => "曾在我校就读，现已毕业",
            "退学" => "曾在我校就读，现已办理退学",
            _ => $"在我校的学籍状态为“{student.Status}”"
        };

        DrawText(canvas, typeface, "证  明", PageWidth / 2, 215, 25, Ink, SKTextAlign.Center, true);
        var body = $"兹证明，{student.Name}（学号：{student.StudentNumber}），" +
                   $"于 {student.EnrollmentDate:yyyy年M月d日} 入学，" +
                   $"就读于{student.CollegeName}{student.MajorName}专业，行政班级为{student.ClassName}，" +
                   $"{statusText}。";
        var y = 285f;
        foreach (var line in WrapText(typeface, body, 16, 448))
        {
            DrawText(canvas, typeface, line, 74, y, 16, Ink);
            y += 32;
        }

        if (!string.IsNullOrWhiteSpace(snapshot.Purpose))
        {
            DrawText(canvas, typeface, $"用途：{snapshot.Purpose}", 74, y + 14, 11, Muted);
            y += 30;
        }

        DrawText(canvas, typeface, "特此证明。", 74, y + 28, 16, Ink);
        DrawText(canvas, typeface, snapshot.InstitutionName, 435, y + 100, 14, Ink,
            SKTextAlign.Center, true);
        DrawText(canvas, typeface, snapshot.IssuingOffice, 435, y + 126, 12, Ink,
            SKTextAlign.Center);
        DrawText(canvas, typeface, snapshot.IssuedAt.ToLocalTime().ToString("yyyy年M月d日"),
            435, y + 150, 11, Muted, SKTextAlign.Center);

        DrawVerificationBlock(canvas, typeface, snapshot, verificationUrl, 625);
        DrawFooter(canvas, typeface, snapshot, 1, 1);
        document.EndPage();
    }

    private static void DrawPageFrame(SKCanvas canvas)
    {
        canvas.Clear(SKColors.White);
        using var border = new SKPaint { Color = Accent, Style = SKPaintStyle.Stroke, StrokeWidth = 1.4f };
        using var inner = new SKPaint { Color = Rule, Style = SKPaintStyle.Stroke, StrokeWidth = .55f };
        canvas.DrawRect(22, 22, PageWidth - 44, PageHeight - 44, border);
        canvas.DrawRect(27, 27, PageWidth - 54, PageHeight - 54, inner);
    }

    private static void DrawHeader(
        SKCanvas canvas,
        SKTypeface typeface,
        OfficialDocumentSnapshot snapshot,
        string title,
        string englishTitle)
    {
        DrawText(canvas, typeface, snapshot.InstitutionName, Margin, 66, 16, Accent, bold: true);
        DrawText(canvas, typeface, snapshot.IssuingOffice, PageWidth - Margin, 66, 10, Muted,
            SKTextAlign.Right);
        DrawText(canvas, typeface, title, PageWidth / 2, 99, 23, Ink, SKTextAlign.Center, true);
        DrawText(canvas, typeface, englishTitle, PageWidth / 2, 119, 8.5f, Muted,
            SKTextAlign.Center);
        using var rule = new SKPaint { Color = Accent, StrokeWidth = 1.2f };
        canvas.DrawLine(Margin, 132, PageWidth - Margin, 132, rule);
    }

    private static float DrawStudentSummary(
        SKCanvas canvas,
        SKTypeface typeface,
        OfficialStudentSnapshot student,
        float y)
    {
        using var fill = new SKPaint { Color = Pale, Style = SKPaintStyle.Fill };
        canvas.DrawRoundRect(new SKRect(Margin, y, PageWidth - Margin, y + 86), 5, 5, fill);
        DrawLabelValue(canvas, typeface, "姓名", student.Name, Margin + 15, y + 24);
        DrawLabelValue(canvas, typeface, "学号", student.StudentNumber, 210, y + 24);
        DrawLabelValue(canvas, typeface, "学籍状态", student.Status, 404, y + 24);
        DrawLabelValue(canvas, typeface, "学院", student.CollegeName, Margin + 15, y + 57);
        DrawLabelValue(canvas, typeface, "专业", student.MajorName, 210, y + 57);
        DrawLabelValue(canvas, typeface, "班级", student.ClassName, 404, y + 57);
        return y + 86;
    }

    private static float DrawTranscriptSummary(
        SKCanvas canvas,
        SKTypeface typeface,
        OfficialDocumentSnapshot snapshot,
        float y)
    {
        var values = new[]
        {
            ("课程门数", snapshot.Grades.Count.ToString()),
            ("修读学分", FormatDecimal(snapshot.TotalCredits)),
            ("获得学分", FormatDecimal(snapshot.EarnedCredits)),
            ("平均绩点", snapshot.GradePointAverage?.ToString("0.00") ?? "-")
        };
        var width = ContentWidth / values.Length;
        for (var i = 0; i < values.Length; i++)
        {
            var center = Margin + width * i + width / 2;
            DrawText(canvas, typeface, values[i].Item1, center, y + 12, 8.5f, Muted,
                SKTextAlign.Center);
            DrawText(canvas, typeface, values[i].Item2, center, y + 33, 15, Accent,
                SKTextAlign.Center, true);
        }
        return y + 42;
    }

    private static float DrawGradeTable(
        SKCanvas canvas,
        SKTypeface typeface,
        IReadOnlyList<OfficialTranscriptRow> rows,
        float y)
    {
        var widths = new[] { 96f, 64f, 160f, 48f, 55f, 45f, 39f };
        var headers = new[] { "学期", "课程代码", "课程名称", "学分", "成绩", "绩点", "状态" };
        const float headerHeight = 27;
        const float rowHeight = 24;
        using var headerPaint = new SKPaint { Color = Accent, Style = SKPaintStyle.Fill };
        using var stripe = new SKPaint { Color = new SKColor(247, 249, 249), Style = SKPaintStyle.Fill };
        using var rule = new SKPaint { Color = Rule, StrokeWidth = .5f };
        canvas.DrawRect(Margin, y, ContentWidth, headerHeight, headerPaint);

        var x = Margin;
        for (var i = 0; i < headers.Length; i++)
        {
            DrawText(canvas, typeface, headers[i], x + widths[i] / 2, y + 18, 8.5f,
                SKColors.White, SKTextAlign.Center, true);
            x += widths[i];
        }

        for (var rowIndex = 0; rowIndex < rows.Count; rowIndex++)
        {
            var row = rows[rowIndex];
            var rowY = y + headerHeight + rowIndex * rowHeight;
            if (rowIndex % 2 == 1)
                canvas.DrawRect(Margin, rowY, ContentWidth, rowHeight, stripe);
            canvas.DrawLine(Margin, rowY + rowHeight, PageWidth - Margin, rowY + rowHeight, rule);
            var values = new[]
            {
                row.AcademicTerm,
                row.CourseCode,
                Ellipsize(typeface, row.CourseName, 8.5f, widths[2] - 8),
                FormatDecimal(row.Credits),
                row.Score?.ToString("0.#") ?? "-",
                row.GradePoint?.ToString("0.0#") ?? "-",
                row.ExamStatus
            };
            x = Margin;
            for (var i = 0; i < values.Length; i++)
            {
                DrawText(canvas, typeface, values[i], x + widths[i] / 2, rowY + 16, 8.5f,
                    Ink, SKTextAlign.Center);
                x += widths[i];
            }
        }
        return y + headerHeight + rows.Count * rowHeight;
    }

    private static void DrawVerificationBlock(
        SKCanvas canvas,
        SKTypeface typeface,
        OfficialDocumentSnapshot snapshot,
        string verificationUrl,
        float y)
    {
        using var fill = new SKPaint { Color = Pale, Style = SKPaintStyle.Fill };
        canvas.DrawRoundRect(new SKRect(Margin, y, PageWidth - Margin, y + 84), 5, 5, fill);
        DrawQrCode(canvas, verificationUrl, Margin + 10, y + 7, 70);
        DrawText(canvas, typeface, "扫码验真", Margin + 94, y + 22, 11, Accent, bold: true);
        DrawText(canvas, typeface, $"凭证编号：{snapshot.DocumentNumber}", Margin + 94, y + 43, 9.5f, Ink);
        DrawText(canvas, typeface, "二维码指向本系统公开验真页；失效或重签后状态会实时更新。",
            Margin + 94, y + 62, 8.5f, Muted);
    }

    private static void DrawFooter(
        SKCanvas canvas,
        SKTypeface typeface,
        OfficialDocumentSnapshot snapshot,
        int page,
        int pageCount)
    {
        using var rule = new SKPaint { Color = Rule, StrokeWidth = .6f };
        canvas.DrawLine(Margin, 790, PageWidth - Margin, 790, rule);
        DrawText(canvas, typeface,
            $"签发：{snapshot.IssuedByName} · {snapshot.IssuedAt.ToLocalTime():yyyy-MM-dd HH:mm}",
            Margin, 807, 7.5f, Muted);
        DrawText(canvas, typeface, $"第 {page} / {pageCount} 页", PageWidth - Margin, 807, 7.5f,
            Muted, SKTextAlign.Right);
    }

    private static void DrawLabelValue(
        SKCanvas canvas,
        SKTypeface typeface,
        string label,
        string value,
        float x,
        float y)
    {
        DrawText(canvas, typeface, label, x, y, 8, Muted);
        DrawText(canvas, typeface, value, x + 42, y, 10, Ink, bold: true);
    }

    private static void DrawQrCode(SKCanvas canvas, string content, float x, float y, float size)
    {
        using var generator = new QRCodeGenerator();
        using var data = generator.CreateQrCode(content, QRCodeGenerator.ECCLevel.Q);
        var modules = data.ModuleMatrix;
        const int quiet = 4;
        var moduleSize = size / (modules.Count + quiet * 2);
        using var paint = new SKPaint { Color = Ink, Style = SKPaintStyle.Fill, IsAntialias = false };
        for (var row = 0; row < modules.Count; row++)
        for (var column = 0; column < modules[row].Count; column++)
        {
            if (!modules[row][column]) continue;
            canvas.DrawRect(
                x + (column + quiet) * moduleSize,
                y + (row + quiet) * moduleSize,
                moduleSize + .08f,
                moduleSize + .08f,
                paint);
        }
    }

    private static void DrawText(
        SKCanvas canvas,
        SKTypeface typeface,
        string text,
        float x,
        float y,
        float size,
        SKColor color,
        SKTextAlign align = SKTextAlign.Left,
        bool bold = false)
    {
        using var font = new SKFont(typeface, size) { Embolden = bold };
        using var paint = new SKPaint { Color = color, IsAntialias = true };
        canvas.DrawText(text, x, y, align, font, paint);
    }

    private static IReadOnlyList<string> WrapText(
        SKTypeface typeface,
        string text,
        float size,
        float maxWidth)
    {
        using var font = new SKFont(typeface, size);
        var lines = new List<string>();
        var current = "";
        foreach (var character in text)
        {
            var candidate = current + character;
            if (current.Length > 0 && font.MeasureText(candidate) > maxWidth)
            {
                var breakIndex = current.LastIndexOfAny([' ', '，', '。', '；', '：', '、']);
                if (breakIndex >= current.Length / 2)
                {
                    lines.Add(current[..(breakIndex + 1)].TrimEnd());
                    current = current[(breakIndex + 1)..].TrimStart() + character;
                }
                else
                {
                    lines.Add(current);
                    current = character.ToString();
                }
            }
            else
            {
                current = candidate;
            }
        }
        if (current.Length > 0) lines.Add(current);
        return lines;
    }

    private static string Ellipsize(
        SKTypeface typeface,
        string text,
        float size,
        float maxWidth)
    {
        using var font = new SKFont(typeface, size);
        if (font.MeasureText(text) <= maxWidth) return text;
        var value = text;
        while (value.Length > 1 && font.MeasureText(value + "…") > maxWidth)
            value = value[..^1];
        return value + "…";
    }

    private static string FormatDecimal(decimal value) => value.ToString("0.##");
}
