using System.Globalization;
using System.Text.Json.Nodes;
using Eis.Application.Administration;
using Eis.Infrastructure.Authentication;
using Eis.Infrastructure.Candidate;
using Eis.Infrastructure.Spreadsheets;

namespace Eis.Infrastructure.Administration;

internal sealed partial class AdminAdmissionService
{
    private const string WorkbookContentType =
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

    public async Task<AdminEndpointResult> GetAdmissionSchoolNoticeTemplateAsync(
        string sessionToken,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAdmissionSchoolAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var data = await LoadAsync(cancellationToken);
        var school = AdmissionSchool(data, context.User!);
        if (school is null) return Error(403, "招生学校账号未绑定有效学校");
        var record = Records(data, "notification")
            .FirstOrDefault(item => item.SchoolId == school.Id && item.Status == "template");
        var template = record?.Payload["template"]?.DeepClone().AsObject() ?? DefaultNoticeTemplate();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["school"] = SchoolJson(school),
            ["exams"] = new JsonArray(data.Operational.Exams
                .Where(item => item.Data["archivedAt"] is null)
                .Select(item => item.Data.DeepClone()).ToArray()),
            ["template"] = template,
            ["updatedAt"] = JsonValue.Create(record?.UpdatedAt)
        });
    }

    public async Task<AdminEndpointResult> SaveAdmissionSchoolNoticeTemplateAsync(
        string sessionToken,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAdmissionSchoolAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var data = await LoadAsync(cancellationToken);
        var school = AdmissionSchool(data, user);
        if (school is null) return Error(403, "招生学校账号未绑定有效学校");
        var requestedExamId = Clean(Text(body["examId"]), 64);
        var exam = data.Operational.Exams.FirstOrDefault(item => item.Id == requestedExamId)
                   ?? data.Operational.Exams.FirstOrDefault(item => item.Data["archivedAt"] is null)
                   ?? data.Operational.Exams.FirstOrDefault();
        if (exam is null) return Error(409, "系统中还没有可关联的考试，暂时无法保存模板");
        var template = new JsonObject
        {
            ["eyebrow"] = Clean(Text(body["eyebrow"]) is { Length: > 0 } eyebrow ? eyebrow : "ADMISSION NOTICE", 60),
            ["title"] = Clean(Text(body["title"]) is { Length: > 0 } title ? title : "录 取 通 知 书", 80),
            ["body"] = Clean(Text(body["body"]), 1600),
            ["footer"] = Clean(Text(body["footer"]), 300),
            ["primaryColor"] = ValidColor(Text(body["primaryColor"])) ? Text(body["primaryColor"]) : "#8d2028",
            ["accentColor"] = ValidColor(Text(body["accentColor"])) ? Text(body["accentColor"]) : "#c9a45b"
        };
        if (Text(template["body"]).Length == 0) return Error(400, "请填写录取通知书正文");
        var now = NowIso();
        var existing = Records(data, "notification")
            .FirstOrDefault(item => item.SchoolId == school.Id && item.Status == "template");
        var record = new CandidateAdmissionRecord(
            existing?.Id ?? Uid("notice_template"),
            "notification",
            exam.Id,
            null,
            school.Id,
            "template",
            new JsonObject
            {
                ["template"] = template.DeepClone(),
                ["updatedBy"] = user.DisplayName
            },
            existing?.CreatedAt ?? now,
            now);
        await repository.SaveRecordsAsync(
            [record],
            Audit(user, "保存录取通知书模板", school.Name),
            cancellationToken);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["template"] = template,
            ["updatedAt"] = now
        });
    }

    public async Task<AdminEndpointResult> GetAdmissionSchoolReportingAsync(
        string sessionToken,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAdmissionSchoolAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var data = await LoadAsync(cancellationToken);
        var school = AdmissionSchool(data, context.User!);
        if (school is null) return Error(403, "招生学校账号未绑定有效学校");
        var batches = Records(data, "plan")
            .Where(item => item.SchoolId == school.Id && item.Status == "approved")
            .Select(plan => ReportingBatch(data, plan, LatestReportingRecord(data, plan.ExamId, school.Id)))
            .ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["school"] = SchoolJson(school),
            ["batches"] = new JsonArray(batches)
        });
    }

    public async Task<AdminDocumentResult> ExportAdmissionSchoolReportingAsync(
        string sessionToken,
        string examId,
        CancellationToken cancellationToken)
    {
        var resolved = await ResolveAdmissionSchoolDocumentAsync(sessionToken, cancellationToken);
        if (resolved.Error is not null) return resolved.Error;
        var data = await LoadAsync(cancellationToken);
        var school = AdmissionSchool(data, resolved.User!);
        if (school is null) return DocumentError(Error(403, "招生学校账号未绑定有效学校"));
        var plan = Records(data, "plan", Clean(examId, 64))
            .FirstOrDefault(item => item.SchoolId == school.Id && item.Status == "approved");
        var record = plan is null ? null : EditableReportingRecord(data, plan.ExamId, school.Id);
        if (plan is null || record is null)
            return DocumentError(Error(404, "当前考试还没有可维护的报到批次"));
        var rows = ReportingRows(data, plan, record).Select(item => new JsonObject
        {
            ["noticeNumber"] = item["noticeNumber"]?.DeepClone(),
            ["candidateNumber"] = item["candidateNumber"]?.DeepClone(),
            ["name"] = item["name"]?.DeepClone(),
            ["examCode"] = item["examCode"]?.DeepClone(),
            ["schoolCode"] = item["schoolCode"]?.DeepClone(),
            ["categoryName"] = item["categoryName"]?.DeepClone(),
            ["reportingStatusCode"] = item["statusCode"]?.DeepClone(),
            ["reportingNote"] = item["note"]?.DeepClone()
        }).ToArray();
        var round = Integer(record.Payload["round"], 1);
        return new AdminDocumentResult(
            200,
            SystemWorkbook.Build("admission_reporting", rows, subtitle: $"{round} 轮｜{school.Name}"),
            WorkbookContentType,
            $"{round}轮-{school.Name}-考生报到状态.xlsx",
            null);
    }

    public async Task<AdminEndpointResult> ImportAdmissionSchoolReportingAsync(
        string sessionToken,
        string examId,
        byte[] content,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAdmissionSchoolAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var data = await LoadAsync(cancellationToken);
        var school = AdmissionSchool(data, user);
        if (school is null) return Error(403, "招生学校账号未绑定有效学校");
        var plan = Records(data, "plan", Clean(examId, 64))
            .FirstOrDefault(item => item.SchoolId == school.Id && item.Status == "approved");
        var record = plan is null ? null : EditableReportingRecord(data, plan.ExamId, school.Id);
        if (plan is null || record is null || record.Status is not ("draft" or "rejected"))
            return Error(409, "当前报到批次不能导入暂存数据");
        IReadOnlyList<JsonObject> imported;
        try
        {
            imported = SystemWorkbook.Parse("admission_reporting", content);
        }
        catch (WorkbookException error)
        {
            return Error(error.StatusCode, error.Message);
        }
        var available = ReportingRows(data, plan, record);
        var byNotice = available.ToDictionary(item => Text(item["noticeNumber"]), StringComparer.Ordinal);
        var byCandidate = available.ToDictionary(item => Text(item["candidateNumber"]), StringComparer.Ordinal);
        var seen = new HashSet<string>(StringComparer.Ordinal);
        var changes = new JsonArray();
        var merged = (record.Payload["rows"] as JsonArray ?? [])
            .OfType<JsonObject>()
            .ToDictionary(item => Text(item["placementId"]), item => item.DeepClone().AsObject(), StringComparer.Ordinal);
        var changedCount = 0;
        var unchangedCount = 0;
        var importedAt = NowIso();
        foreach (var item in imported)
        {
            var rowNumber = Integer(item["__row"]);
            var noticeNumber = Clean(Text(item["noticeNumber"]), 100);
            var candidateNumber = Clean(Text(item["candidateNumber"]), 100);
            if (!byNotice.TryGetValue(noticeNumber, out var target) ||
                !byCandidate.TryGetValue(candidateNumber, out var candidateTarget) ||
                Text(candidateTarget["placementId"]) != Text(target["placementId"]))
                return Error(400, $"Excel 第 {rowNumber} 行的通知书编号与报名号不属于本校当前报到批次");
            var placementId = Text(target["placementId"]);
            if (!seen.Add(placementId)) return Error(400, $"Excel 第 {rowNumber} 行重复填写同一考生");
            var code = Text(item["reportingStatusCode"]).Trim().ToUpperInvariant();
            var status = code switch { "Y" => "reported", "N" => "not_reported", "P" => "pending", _ => "" };
            if (status.Length == 0) return Error(400, $"Excel 第 {rowNumber} 行报到状态码只能填写 Y、N 或 P");
            var note = Clean(Text(item["reportingNote"]), 300);
            if (Text(target["status"]) == status && Text(target["note"]) == note)
            {
                unchangedCount++;
                continue;
            }
            merged[placementId] = new JsonObject
            {
                ["placementId"] = placementId,
                ["status"] = status,
                ["note"] = note,
                ["updatedAt"] = importedAt,
                ["source"] = "excel"
            };
            changes.Add(new JsonObject
            {
                ["placementId"] = placementId,
                ["name"] = target["name"]?.DeepClone(),
                ["candidateNumber"] = candidateNumber,
                ["noticeNumber"] = noticeNumber,
                ["from"] = target["status"]?.DeepClone(),
                ["to"] = status,
                ["fromCode"] = target["statusCode"]?.DeepClone(),
                ["toCode"] = code,
                ["noteChanged"] = Text(target["note"]) != note
            });
            changedCount++;
        }
        var updated = record;
        if (changedCount > 0)
        {
            var payload = record.Payload.DeepClone().AsObject();
            payload["rows"] = new JsonArray(merged.Values.Select(item => (JsonNode)item).ToArray());
            payload["lastImportedAt"] = importedAt;
            payload["lastImportedBy"] = user.DisplayName;
            updated = record with { Status = "draft", UpdatedAt = importedAt, Payload = payload };
            await repository.SaveRecordsAsync(
                [updated],
                Audit(user, "Excel 暂存考生报到状态", $"{school.Name} · 实际更新 {changedCount} 人"),
                cancellationToken);
            data = ReplaceRecord(data, updated);
        }
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["count"] = imported.Count,
            ["changedCount"] = changedCount,
            ["unchangedCount"] = unchangedCount,
            ["changes"] = changes,
            ["batch"] = ReportingBatch(data, plan, updated)
        });
    }

    public async Task<AdminEndpointResult> GetAdmissionSchoolPlacementsAsync(
        string sessionToken,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAdmissionSchoolAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var data = await LoadAsync(cancellationToken);
        var school = AdmissionSchool(data, context.User!);
        if (school is null) return Error(403, "招生学校账号未绑定有效学校");
        var placements = Records(data, "placement").Where(item => item.SchoolId == school.Id)
            .Select(item => AdmissionSchoolPlacement(data, item)).ToArray();
        var completed = data.Operational.Exams.Where(exam =>
            Records(data, "setting", exam.Id).Any(setting => setting.Status == "completed") &&
            placements.Any(item => Text(item["examId"]) == exam.Id && Text(item["status"]) == "final"))
            .Select(item => item.Data.DeepClone()).ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["school"] = SchoolJson(school),
            ["placements"] = new JsonArray(placements),
            ["completedExams"] = new JsonArray(completed)
        });
    }

    public async Task<AdminDocumentResult> ExportAdmissionSchoolPlacementsAsync(
        string sessionToken,
        string examId,
        CancellationToken cancellationToken)
    {
        var resolved = await ResolveAdmissionSchoolDocumentAsync(sessionToken, cancellationToken);
        if (resolved.Error is not null) return resolved.Error;
        var data = await LoadAsync(cancellationToken);
        var school = AdmissionSchool(data, resolved.User!);
        if (school is null) return DocumentError(Error(403, "招生学校账号未绑定有效学校"));
        var cleanExamId = Clean(examId, 64);
        var exam = data.Operational.Exams.FirstOrDefault(item => item.Id == cleanExamId);
        var setting = Records(data, "setting", cleanExamId).FirstOrDefault();
        if (exam is null || setting?.Status != "completed")
            return DocumentError(Error(409, "录取工作结束后才能下载正式录取名单"));
        var rows = Records(data, "placement", cleanExamId)
            .Where(item => item.SchoolId == school.Id && item.Status == "final")
            .Select(item => AdmittedCandidateRow(data, exam, school, item)).ToArray();
        var examName = Text(exam.Data["name"]);
        return new AdminDocumentResult(
            200,
            SystemWorkbook.Build("admitted_candidates", rows, subtitle: $"{examName}｜{school.Name}"),
            WorkbookContentType,
            $"{examName}-{school.Name}-录取考生信息.xlsx",
            null);
    }

    public async Task<AdminDocumentResult> ExportAdminLedgerAsync(
        string sessionToken,
        string kind,
        IReadOnlyDictionary<string, string> filters,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, "只有超级管理员可以导出志愿与录取台账", cancellationToken);
        if (context.Error is not null) return DocumentError(context.Error);
        if (kind is not ("preferences" or "placements"))
            return DocumentError(Error(404, "招生台账类型不存在"));
        var data = await LoadAsync(cancellationToken);
        var selectedExam = data.Operational.Exams.FirstOrDefault(item =>
            item.Id == filters.GetValueOrDefault("examId"));
        var subtitle = $"{(selectedExam is null ? "全部考试" : Text(selectedExam.Data["name"]))}｜按当前筛选条件导出｜生成时间 {DateTime.Now:yyyy/M/d HH:mm:ss}";
        IReadOnlyList<JsonObject> rows;
        string resource;
        string fileName;
        if (kind == "preferences")
        {
            rows = FilterLedgerRows(PreferenceSnapshotRows(data), filters).SelectMany(item =>
            {
                var choices = (item["choices"] as JsonArray ?? []).OfType<JsonObject>().ToArray();
                if (choices.Length == 0) choices = [new JsonObject()];
                return choices.Select((choice, index) => new JsonObject
                {
                    ["examCode"] = item["examCode"]?.DeepClone(),
                    ["examName"] = item["examName"]?.DeepClone(),
                    ["round"] = item["round"]?.DeepClone(),
                    ["fillStatus"] = item["fillStatus"]?.DeepClone(),
                    ["lockStatus"] = item["lockStatus"]?.DeepClone(),
                    ["submissionCount"] = item["submissionCount"]?.DeepClone(),
                    ["maxSubmissions"] = item["maxSubmissions"]?.DeepClone(),
                    ["candidateNumber"] = item["candidate"]?["registrationNumber"]?.DeepClone(),
                    ["candidateName"] = item["candidate"]?["name"]?.DeepClone(),
                    ["sourceSchoolCode"] = item["sourceSchoolCode"]?.DeepClone(),
                    ["sourceSchoolName"] = item["sourceSchoolName"]?.DeepClone(),
                    ["className"] = item["className"]?.DeepClone(),
                    ["specialty"] = item["specialty"]?.DeepClone(),
                    ["indicatorStatus"] = item["indicatorStatus"]?.DeepClone(),
                    ["preferenceOrder"] = choice.Count == 0 ? "" : Integer(choice["order"], index + 1),
                    ["preferenceType"] = choice.Count == 0 ? "" :
                        Text(choice["preferenceType"]) == "indicator" ? "指标志愿" : "普通志愿",
                    ["targetSchoolCode"] = Text(choice["schoolCode"]),
                    ["targetSchoolName"] = Text(choice["schoolName"]),
                    ["categoryName"] = Text(choice["categoryName"]) is { Length: > 0 } category
                        ? category : Text(choice["categoryCode"]),
                    ["submittedAt"] = item["submittedAt"]?.DeepClone()
                });
            }).ToArray();
            resource = "admission_preferences";
            fileName = $"志愿填报实时台账-{DateTime.UtcNow:yyyy-MM-dd}.xlsx";
        }
        else
        {
            var placements = Records(data, "placement").Select(item => PlacementJson(data, item));
            rows = FilterLedgerRows(placements, filters).Select(item =>
            {
                var payload = item["payload"] as JsonObject ?? new JsonObject();
                return new JsonObject
                {
                    ["examCode"] = item["examCode"]?.DeepClone(),
                    ["examName"] = item["examName"]?.DeepClone(),
                    ["round"] = Integer(payload["round"], 1),
                    ["candidateNumber"] = item["candidate"]?["registrationNumber"]?.DeepClone(),
                    ["candidateName"] = item["candidate"]?["name"]?.DeepClone(),
                    ["sourceSchoolCode"] = item["sourceSchoolCode"]?.DeepClone(),
                    ["sourceSchoolName"] = item["sourceSchoolName"]?.DeepClone(),
                    ["className"] = item["className"]?.DeepClone(),
                    ["specialty"] = item["candidate"]?["specialtyLabel"]?.DeepClone(),
                    ["culturalScore"] = Number(payload["culturalScore"]) is var cultural && cultural != 0
                        ? cultural : Number(payload["totalScore"]),
                    ["featureScore"] = Number(payload["featureScore"]),
                    ["totalScore"] = Number(payload["totalScore"]),
                    ["preferenceOrder"] = Integer(payload["preferenceOrder"]),
                    ["admissionSchoolCode"] = item["schoolCode"]?.DeepClone(),
                    ["admissionSchoolName"] = item["schoolName"]?.DeepClone(),
                    ["categoryName"] = payload["categoryName"]?.DeepClone(),
                    ["quotaBucket"] = Text(payload["quotaBucket"]).StartsWith("indicator", StringComparison.Ordinal)
                        ? "指标分配" : "普通计划",
                    ["admissionStatus"] = item["admissionStatusLabel"]?.DeepClone(),
                    ["reportingStatus"] = item["reportingStatusLabel"]?.DeepClone(),
                    ["noticeNumber"] = payload["noticeNumber"]?.DeepClone(),
                    ["withdrawalReason"] = Text(payload["withdrawalReason"]) is { Length: > 0 } reason
                        ? reason : Text(payload["reportingNote"]),
                    ["updatedAt"] = Text(item["updatedAt"]) is { Length: > 0 } updated
                        ? updated : Text(payload["finalizedAt"])
                };
            }).ToArray();
            resource = "admission_placements";
            fileName = $"招生录取情况台账-{DateTime.UtcNow:yyyy-MM-dd}.xlsx";
        }
        return new AdminDocumentResult(
            200,
            SystemWorkbook.Build(resource, rows, subtitle: subtitle),
            WorkbookContentType,
            fileName,
            null);
    }

    private async Task<ResolvedAdmin> ResolveAdmissionSchoolAsync(
        string token,
        CancellationToken cancellationToken)
    {
        if (token.Length == 0) return ResolvedAdmin.Failed(Error(401, "请先登录"));
        var userId = await authenticationState.GetSessionUserIdAsync(token);
        if (userId is null) return ResolvedAdmin.Failed(Error(401, "请先登录"));
        var user = await authenticationRepository.FindUserByIdAsync(userId, cancellationToken);
        if (user is not { Active: true, ArchivedAt: null })
            return ResolvedAdmin.Failed(Error(401, "请先登录"));
        return user.Role == "admission_school"
            ? new ResolvedAdmin(user, null)
            : ResolvedAdmin.Failed(Error(403, "当前账号不是招生学校账号"));
    }

    private async Task<(AuthenticationUser? User, AdminDocumentResult? Error)> ResolveAdmissionSchoolDocumentAsync(
        string token,
        CancellationToken cancellationToken)
    {
        var result = await ResolveAdmissionSchoolAsync(token, cancellationToken);
        return (result.User, result.Error is null ? null : DocumentError(result.Error));
    }

    private static AdminSchool? AdmissionSchool(AdmissionData data, AuthenticationUser user) =>
        data.Directory.Schools.FirstOrDefault(item =>
            item.Id == user.SchoolId && item.Active && item.IsAdmissionSchool);

    private static CandidateAdmissionRecord? LatestReportingRecord(
        AdmissionData data,
        string examId,
        string schoolId) =>
        Records(data, "notification", examId)
            .Where(item => item.SchoolId == schoolId && item.UserId is null &&
                           Text(item.Payload["type"]) == "admission_reporting")
            .OrderByDescending(item => Integer(item.Payload["round"], 1))
            .ThenByDescending(item => item.UpdatedAt, StringComparer.Ordinal)
            .FirstOrDefault();

    private static CandidateAdmissionRecord? EditableReportingRecord(
        AdmissionData data,
        string examId,
        string schoolId)
    {
        var round = Integer(Records(data, "setting", examId).FirstOrDefault()?.Payload["round"], 1);
        return ReportingRecord(data, examId, schoolId, round) ??
               LatestReportingRecord(data, examId, schoolId);
    }

    private static JsonObject[] ReportingRows(
        AdmissionData data,
        CandidateAdmissionRecord plan,
        CandidateAdmissionRecord? record)
    {
        var exam = data.Operational.Exams.FirstOrDefault(item => item.Id == plan.ExamId);
        var school = School(data, plan.SchoolId);
        var rowByPlacement = (record?.Payload["rows"] as JsonArray ?? []).OfType<JsonObject>()
            .ToDictionary(item => Text(item["placementId"]), StringComparer.Ordinal);
        var placementIds = rowByPlacement.Keys.ToHashSet(StringComparer.Ordinal);
        var round = Integer(record?.Payload["round"], 1);
        return Records(data, "placement", plan.ExamId)
            .Where(item => item.SchoolId == plan.SchoolId && item.Status == "final" &&
                           (placementIds.Contains(item.Id) ||
                            (record is null && Integer(item.Payload["finalizedRound"], 1) == round)))
            .Select(item =>
            {
                rowByPlacement.TryGetValue(item.Id, out var row);
                var account = data.Directory.Users.FirstOrDefault(user => user.Id == item.UserId);
                var profile = data.Operational.Profiles.FirstOrDefault(profile => profile.UserId == item.UserId);
                var status = Text(row?["status"]) is { Length: > 0 } current ? current : "pending";
                return new JsonObject
                {
                    ["placementId"] = item.Id,
                    ["noticeNumber"] = Text(item.Payload["noticeNumber"]),
                    ["candidateNumber"] = account?.CandidateNumber ?? "",
                    ["name"] = Text(profile?.Data["name"]) is { Length: > 0 } name ? name : account?.DisplayName ?? "",
                    ["idNumberMasked"] = MaskId(Text(profile?.Data["idNumber"])),
                    ["examCode"] = Text(exam?.Data["code"]),
                    ["schoolCode"] = school?.Code ?? "",
                    ["categoryName"] = Text(item.Payload["categoryName"]),
                    ["status"] = status,
                    ["statusCode"] = status switch { "reported" => "Y", "not_reported" => "N", _ => "P" },
                    ["note"] = Text(row?["note"]),
                    ["updatedAt"] = JsonValue.Create(row?["updatedAt"])
                };
            })
            .OrderBy(item => Text(item["candidateNumber"]), StringComparer.Ordinal)
            .ToArray();
    }

    private static JsonObject ReportingBatch(
        AdmissionData data,
        CandidateAdmissionRecord plan,
        CandidateAdmissionRecord? record)
    {
        var exam = data.Operational.Exams.FirstOrDefault(item => item.Id == plan.ExamId);
        return new JsonObject
        {
            ["id"] = record?.Id ?? "",
            ["exam"] = new JsonObject
            {
                ["id"] = exam?.Id ?? plan.ExamId,
                ["code"] = Text(exam?.Data["code"]),
                ["name"] = Text(exam?.Data["name"])
            },
            ["round"] = Integer(record?.Payload["round"], 1),
            ["status"] = record?.Status ?? "not_started",
            ["rows"] = new JsonArray(ReportingRows(data, plan, record)),
            ["progress"] = PlanProgress(data, plan),
            ["supplementDecision"] = Text(record?.Payload["supplementDecision"]),
            ["decisionNote"] = Text(record?.Payload["decisionNote"]),
            ["approvalNote"] = Text(record?.Payload["approvalNote"]),
            ["updatedAt"] = JsonValue.Create(record?.UpdatedAt)
        };
    }

    private static JsonObject AdmissionSchoolPlacement(AdmissionData data, CandidateAdmissionRecord item)
    {
        var output = RecordJson(item);
        var profile = data.Operational.Profiles.FirstOrDefault(entry => entry.UserId == item.UserId);
        var account = data.Directory.Users.FirstOrDefault(entry => entry.Id == item.UserId);
        var registration = data.Operational.Registrations.FirstOrDefault(entry =>
            entry.ExamId == item.ExamId && entry.UserId == item.UserId);
        var exam = data.Operational.Exams.FirstOrDefault(entry => entry.Id == item.ExamId);
        var results = registration is null ? [] : data.Results.Where(entry =>
            entry.RegistrationId == registration.Id && entry.Published).Select(entry => (JsonNode)new JsonObject
        {
            ["subjectName"] = Text(exam?.Subjects.FirstOrDefault(subject => Text(subject["id"]) == entry.SubjectId)?["name"]) is { Length: > 0 } subjectName ? subjectName : entry.SubjectId,
            ["score"] = entry.Score
        }).ToArray();
        output["examName"] = Text(exam?.Data["name"]) is { Length: > 0 } examName ? examName : item.ExamId;
        output["candidate"] = new JsonObject
        {
            ["registrationNumber"] = account?.CandidateNumber ?? "",
            ["name"] = Text(profile?.Data["name"]),
            ["gender"] = Text(profile?.Data["gender"]),
            ["idNumberMasked"] = MaskId(Text(profile?.Data["idNumber"])),
            ["specialtyCategory"] = ResolveSpecialty(profile?.Data ?? new JsonObject()).Category,
            ["specialtyType"] = ResolveSpecialty(profile?.Data ?? new JsonObject()).Type,
            ["specialtyLabel"] = SpecialtyLabel(
                ResolveSpecialty(profile?.Data ?? new JsonObject()).Category,
                ResolveSpecialty(profile?.Data ?? new JsonObject()).Type),
            ["specialtyCertificate"] = Text(profile?.Data["specialtyCertificate"]),
            ["policyEligibility"] = Text(profile?.Data["policyEligibility"])
        };
        output["featureScore"] = Number(registration?.Data["featureScore"]);
        output["results"] = new JsonArray(results);
        return output;
    }

    private static JsonObject AdmittedCandidateRow(
        AdmissionData data,
        OperationalExam exam,
        AdminSchool school,
        CandidateAdmissionRecord item)
    {
        var profile = data.Operational.Profiles.FirstOrDefault(entry => entry.UserId == item.UserId);
        var account = data.Directory.Users.FirstOrDefault(entry => entry.Id == item.UserId);
        var registration = data.Operational.Registrations.FirstOrDefault(entry =>
            entry.ExamId == exam.Id && entry.UserId == item.UserId);
        var sourceSchool = School(data, profile?.SchoolId);
        var schoolClass = data.Directory.Classes.FirstOrDefault(entry => entry.Id == profile?.ClassId);
        var specialty = ResolveSpecialty(profile?.Data ?? new JsonObject());
        var scores = registration is null ? [] : data.Results.Where(entry =>
            entry.RegistrationId == registration.Id && entry.Published).ToArray();
        string SubjectName(string id) =>
            Text(exam.Subjects.FirstOrDefault(subject => Text(subject["id"]) == id)?["name"]) is { Length: > 0 } value
                ? value : id;
        return new JsonObject
        {
            ["candidateNumber"] = account?.CandidateNumber ?? Text(registration?.Data["registrationNumber"]),
            ["name"] = Text(profile?.Data["name"]) is { Length: > 0 } name ? name : account?.DisplayName ?? "",
            ["gender"] = Text(profile?.Data["gender"]),
            ["idNumber"] = Text(profile?.Data["idNumber"]),
            ["phone"] = Text(profile?.Data["phone"]),
            ["email"] = Text(profile?.Data["email"]),
            ["birthDate"] = Text(profile?.Data["birthDate"]),
            ["ethnicity"] = Text(profile?.Data["ethnicity"]),
            ["nativePlace"] = Text(profile?.Data["nativePlace"]),
            ["sourceSchoolCode"] = sourceSchool?.Code ?? "",
            ["sourceSchool"] = sourceSchool?.Name ?? Text(profile?.Data["school"]),
            ["className"] = schoolClass?.Name ?? Text(profile?.Data["grade"]),
            ["address"] = string.Join(' ', new[]
            {
                Text(profile?.Data["provinceName"]), Text(profile?.Data["cityName"]),
                Text(profile?.Data["districtName"]), Text(profile?.Data["address"])
            }.Where(value => value.Length > 0)),
            ["guardianName"] = Text(profile?.Data["guardianName"]) is { Length: > 0 } guardian
                ? guardian : Text(profile?.Data["emergencyContact"]),
            ["guardianPhone"] = Text(profile?.Data["guardianPhone"]) is { Length: > 0 } phone
                ? phone : Text(profile?.Data["emergencyPhone"]),
            ["specialty"] = SpecialtyLabel(specialty.Category, specialty.Type),
            ["specialtyCertificate"] = Text(profile?.Data["specialtyCertificate"]),
            ["policyEligibility"] = Text(profile?.Data["policyEligibility"]),
            ["featureScore"] = Number(registration?.Data["featureScore"]),
            ["subjectScores"] = string.Join('；', scores.Select(score =>
                $"{SubjectName(score.SubjectId)} {score.Score.ToString("0.##", CultureInfo.InvariantCulture)}")),
            ["totalScore"] = Number(item.Payload["totalScore"]),
            ["admittedSchool"] = school.Name,
            ["categoryName"] = Text(item.Payload["categoryName"]),
            ["preferenceOrder"] = Integer(item.Payload["preferenceOrder"])
        };
    }

    private static AdmissionData ReplaceRecord(AdmissionData data, CandidateAdmissionRecord updated)
    {
        var records = data.Admission.Records.Select(item => item.Id == updated.Id ? updated : item).ToArray();
        return data with { Admission = data.Admission with { Records = records } };
    }

    private static JsonObject DefaultNoticeTemplate() => new()
    {
        ["eyebrow"] = "ADMISSION NOTICE",
        ["title"] = "录 取 通 知 书",
        ["body"] = "经审核，你已被我校 {{录取类别}} 正式录取。谨向你表示祝贺！请按学校通知要求办理报到手续。",
        ["footer"] = "请妥善保管本通知书，报到时出示。",
        ["primaryColor"] = "#8d2028",
        ["accentColor"] = "#c9a45b"
    };

    private static bool ValidColor(string value) =>
        value.Length == 7 && value[0] == '#' && value[1..].All(Uri.IsHexDigit);

    private static IEnumerable<JsonObject> FilterLedgerRows(
        IEnumerable<JsonObject> rows,
        IReadOnlyDictionary<string, string> filters)
    {
        var query = filters.GetValueOrDefault("q", "").Trim();
        foreach (var item in rows)
        {
            var payload = item["payload"] as JsonObject;
            var matches = new[] { "examId", "schoolId", "sourceSchoolId", "status", "round" }.All(key =>
            {
                var expected = filters.GetValueOrDefault(key, "");
                return expected.Length == 0 || Text(item[key]) == expected || Text(payload?[key]) == expected;
            });
            if (!matches) continue;
            var haystack = item.ToJsonString().ToLowerInvariant();
            if (query.Length > 0 && query.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)
                    .Any(word => !haystack.Contains(word.ToLowerInvariant(), StringComparison.Ordinal)))
                continue;
            yield return item;
        }
    }

    private static AdminDocumentResult DocumentError(AdminEndpointResult error) =>
        new(error.StatusCode, null, null, null, error.Body);
}
