using System.Globalization;
using System.Text.Json.Nodes;
using ClosedXML.Excel;

namespace Eis.Infrastructure.Spreadsheets;

internal sealed record WorkbookColumn(
    string Key,
    string Header,
    double Width,
    object Example,
    string NumberFormat = "@",
    string[]? Validation = null);

internal sealed record WorkbookSpec(string Title, string Sheet, WorkbookColumn[] Columns);

internal static class SystemWorkbook
{
    private static readonly IReadOnlyDictionary<string, WorkbookSpec> Specs =
        new Dictionary<string, WorkbookSpec>(StringComparer.Ordinal)
        {
            ["classes"] = new("班级台账", "班级",
            [
                C("schoolCode", "学校代码*", 16, "HZ01"), C("grade", "年级*", 14, "九年级"),
                C("name", "班级名称*", 22, "九年级（3）班"), C("status", "状态*", 12, "启用", values: ["启用", "停用"])
            ]),
            ["class_admins"] = new("班级管理员台账", "班级管理员",
            [
                C("schoolCode", "学校代码*", 16, "HZ01"), C("className", "班级名称*", 22, "九年级（1）班"),
                C("displayName", "管理员姓名*", 18, "张老师"), C("username", "登录账号*", 20, "hz01_g901"),
                C("initialPassword", "初始密码（新建必填）", 24, "ChangeMe123!"),
                C("status", "状态*", 12, "启用", values: ["启用", "停用"])
            ]),
            ["account_quotas"] = new("批量报名号申领配额", "班级配额",
            [
                C("className", "班级名称*", 24, "九年级（1）班"),
                C("count", "申领数量*", 16, 30, "0")
            ]),
            ["account_results"] = new("报名号下发清单", "账号结果",
            [
                C("batchId", "批次编号", 30, ""), C("className", "班级", 22, ""),
                C("candidateNumber", "固定报名号", 26, ""), C("initialPassword", "初始密码", 22, "")
            ]),
            ["candidates"] = new("考生资料台账", "考生资料",
            [
                C("candidateNumber", "报名号*", 26, "2026-HZ01-X-0001"), C("name", "姓名*", 16, "李明"),
                C("gender", "性别*", 10, "男", values: ["男", "女"]), C("idNumber", "证件号码*", 24, "320101201101011234"),
                C("phone", "手机号*", 18, "13800138000"), C("email", "邮箱", 24, "student@example.com"),
                C("nativePlace", "籍贯", 18, "江苏海州"), C("provinceCode", "省级代码*", 14, "320000"),
                C("provinceName", "省份", 18, "江苏省"), C("cityCode", "市级代码*", 14, "320700"),
                C("cityName", "城市", 18, "连云港市"), C("districtCode", "区县代码*", 14, "320706"),
                C("districtName", "区县", 18, "海州区"), C("address", "详细住址*", 32, "示例路 1 号"),
                C("className", "班级*", 22, "九年级（1）班"), C("ethnicity", "民族", 12, "汉族"),
                C("birthDate", "出生日期", 16, "2011-01-01"), C("postalCode", "邮编", 14, "222000"),
                C("guardianName", "监护人", 16, "李家长"), C("guardianPhone", "监护人电话", 18, "13900139000")
            ]),
            ["payments"] = new("考试缴费名单", "缴费名单",
            [
                C("examCode", "考试代码", 18, ""), C("examName", "考试名称", 28, ""),
                C("schoolName", "学校", 24, ""), C("className", "班级", 20, ""),
                C("candidateNumber", "报名号", 26, ""), C("candidateName", "考生姓名", 16, ""),
                C("subjectNames", "报考科目", 34, ""), C("amountDue", "应缴金额（元）", 18, 0, "0.00"),
                C("paymentStatus", "缴费状态", 14, ""), C("paidAt", "确认时间", 24, ""),
                C("paidByName", "确认人", 16, "")
            ]),
            ["centers"] = new("考点考场档案", "考点考场",
            [
                C("schoolCode", "学校代码*", 14, "HZ01"), C("centerCode", "考点代码*", 18, "HZ01-C02"),
                C("centerName", "考点名称*", 24, "第一中学考点"), C("provinceCode", "省级代码*", 14, "320000"),
                C("provinceName", "省份", 18, "江苏省"), C("cityCode", "市级代码*", 14, "320700"),
                C("cityName", "城市", 18, "连云港市"), C("districtCode", "区县代码*", 14, "320706"),
                C("districtName", "区县", 18, "海州区"), C("address", "详细地址*", 30, "示例路 8 号"),
                C("managerName", "负责人", 16, "王老师"), C("managerPhone", "负责人手机", 18, "13800138000"),
                C("contact", "值班电话", 18, "0518-86020000"), C("emergencyPhone", "应急电话", 18, "0518-120"),
                C("gateOpenTime", "开放时间", 14, "07:00"), C("transport", "交通提示", 30, "东门入场"),
                C("centerStatus", "考点状态*", 14, "启用", values: ["启用", "停用"]), C("centerNotes", "考点备注", 26, ""),
                C("roomCode", "考场代码*", 16, "001"), C("roomName", "考场名称*", 22, "第 001 考场"),
                C("building", "楼栋*", 18, "教学楼 A"), C("floor", "楼层", 12, "1 层"),
                C("capacity", "容量*", 12, 30, "0"), C("seatPlan", "座位编排说明", 28, "按现场座次表编排"),
                C("roomType", "考场类型*", 16, "标准考场", values: ["标准考场", "机考考场", "无障碍考场", "备用考场"]),
                C("roomStatus", "考场状态*", 14, "启用", values: ["启用", "停用"]), C("roomNotes", "考场备注", 26, "")
            ]),
            ["results"] = new("考试成绩台账", "成绩",
            [
                C("candidateNumber", "报名号*", 26, "2026-HZ01-X-0001"), C("cardNumber", "准考证号（只读参考）", 24, ""),
                C("candidateName", "姓名（只读参考）", 16, ""), C("schoolName", "学校（只读参考）", 24, ""),
                C("className", "班级（只读参考）", 20, ""), C("examCode", "考试代码*", 20, "EX-2026-AUT"),
                C("examName", "考试名称（只读参考）", 28, ""), C("subjectName", "科目*", 16, "语文"),
                C("fullScore", "科目满分（只读参考）", 18, 0, "0.00"), C("passRule", "单科及格规则（只读参考）", 26, ""),
                C("passScore", "实际及格分（只读参考）", 20, 0, "0.00"), C("score", "成绩*", 12, 120, "0.00"),
                C("rank", "本科排名（导出计算）", 18, 0, "0"), C("rankPercent", "排名百分位（导出计算）", 20, 0, "0.00"),
                C("qualified", "单科达线（导出计算）", 16, ""), C("grade", "排名等级（自动计算）", 18, ""),
                C("published", "发布状态*", 14, "发布", values: ["发布", "不发布"]), C("updatedAt", "更新时间（只读参考）", 24, "")
            ]),
            ["admission_reporting"] = new("录取考生报到状态维护表", "考生报到",
            [
                C("noticeNumber", "录取通知书编号*", 34, "AD01-EX-2026-ZK-000001"),
                C("candidateNumber", "报名号*", 26, "2026-HZ01-F-0001"), C("name", "姓名（只读）", 14, "张同学"),
                C("examCode", "考试代码（只读）", 20, "EX-2026-ZK"), C("schoolCode", "招生学校代码（只读）", 18, "AD01"),
                C("categoryName", "录取类别（只读）", 20, "普通生"),
                C("reportingStatusCode", "报到状态码*（Y/N/P）", 22, "P", values: ["Y", "N", "P"]),
                C("reportingNote", "报到备注", 36, "")
            ]),
            ["admitted_candidates"] = new("录取考生信息表", "录取考生",
            [
                C("candidateNumber", "报名号", 26, ""), C("name", "姓名", 14, ""), C("gender", "性别", 10, ""),
                C("idNumber", "证件号码", 24, ""), C("phone", "手机号", 18, ""), C("email", "邮箱", 24, ""),
                C("birthDate", "出生日期", 14, ""), C("ethnicity", "民族", 12, ""), C("nativePlace", "籍贯", 18, ""),
                C("sourceSchoolCode", "生源学校代码", 16, ""), C("sourceSchool", "生源学校", 26, ""), C("className", "班级", 18, ""),
                C("address", "家庭住址", 36, ""), C("guardianName", "监护人", 14, ""), C("guardianPhone", "监护人电话", 18, ""),
                C("specialty", "特长生资格", 20, ""), C("specialtyCertificate", "特长证明编号", 20, ""),
                C("policyEligibility", "政策资格说明", 24, ""), C("featureScore", "特征分", 12, 0, "0.00"),
                C("subjectScores", "各科成绩", 42, ""), C("totalScore", "考生总成绩", 14, 0, "0.00"),
                C("admittedSchool", "录取学校", 26, ""), C("categoryName", "录取类别", 20, ""),
                C("preferenceOrder", "志愿序号", 12, 0, "0")
            ]),
            ["admission_preferences"] = new("志愿填报实时台账", "志愿填报",
            [
                C("examCode", "考试代码", 18, ""), C("examName", "考试名称", 28, ""), C("round", "轮次", 10, 1, "0"),
                C("fillStatus", "填报状态", 14, ""), C("lockStatus", "锁定状态", 14, ""),
                C("submissionCount", "已提交次数", 14, 0, "0"), C("maxSubmissions", "最多提交次数", 16, 0, "0"),
                C("candidateNumber", "报名号", 26, ""), C("candidateName", "考生姓名", 16, ""),
                C("sourceSchoolCode", "生源学校代码", 16, ""), C("sourceSchoolName", "生源学校", 26, ""),
                C("className", "班级", 18, ""), C("specialty", "特长生资格", 20, ""),
                C("indicatorStatus", "指标资格", 14, ""), C("preferenceOrder", "志愿序号", 12, "", "@"),
                C("preferenceType", "志愿类型", 14, ""), C("targetSchoolCode", "目标学校代码", 18, ""),
                C("targetSchoolName", "目标学校", 26, ""), C("categoryName", "招生类别", 20, ""),
                C("submittedAt", "提交时间", 24, "")
            ]),
            ["admission_placements"] = new("招生录取情况台账", "招生录取",
            [
                C("examCode", "考试代码", 18, ""), C("examName", "考试名称", 28, ""), C("round", "轮次", 10, 1, "0"),
                C("candidateNumber", "报名号", 26, ""), C("candidateName", "考生姓名", 16, ""),
                C("sourceSchoolCode", "生源学校代码", 16, ""), C("sourceSchoolName", "生源学校", 26, ""),
                C("className", "班级", 18, ""), C("specialty", "特长生资格", 20, ""),
                C("culturalScore", "文化成绩", 14, 0, "0.00"), C("featureScore", "特征分", 12, 0, "0.00"),
                C("totalScore", "投档总分", 14, 0, "0.00"), C("preferenceOrder", "志愿序号", 12, 0, "0"),
                C("admissionSchoolCode", "招生学校代码", 16, ""), C("admissionSchoolName", "招生学校", 26, ""),
                C("categoryName", "招生类别", 20, ""), C("quotaBucket", "计划类型", 14, ""),
                C("admissionStatus", "录取状态", 14, ""), C("reportingStatus", "报到状态", 14, ""),
                C("noticeNumber", "通知书编号", 34, ""), C("withdrawalReason", "退档或未报到原因", 32, ""),
                C("updatedAt", "更新时间", 24, "")
            ])
        };

    public static bool Supports(string resource) => Specs.ContainsKey(resource);

    public static byte[] Build(
        string resource,
        IReadOnlyList<JsonObject> rows,
        bool template = false,
        string subtitle = "")
    {
        var spec = Get(resource);
        using var workbook = new XLWorkbook();
        workbook.Properties.Author = "衡准考试信息管理系统";
        var sheet = workbook.Worksheets.Add(spec.Sheet);
        sheet.ShowGridLines = false;
        sheet.SheetView.FreezeRows(2);
        var title = sheet.Range(1, 1, 1, spec.Columns.Length);
        title.Merge();
        title.Value = subtitle.Length > 0 ? $"{spec.Title}｜{subtitle}" : spec.Title;
        title.Style.Fill.BackgroundColor = XLColor.FromHtml("#173F60");
        title.Style.Font.FontName = "微软雅黑";
        title.Style.Font.FontSize = 16;
        title.Style.Font.Bold = true;
        title.Style.Font.FontColor = XLColor.White;
        title.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
        sheet.Row(1).Height = 34;
        for (var index = 0; index < spec.Columns.Length; index++)
        {
            var column = spec.Columns[index];
            var cell = sheet.Cell(2, index + 1);
            cell.Value = column.Header;
            cell.Style.Fill.BackgroundColor = XLColor.FromHtml("#2C7080");
            cell.Style.Font.FontName = "微软雅黑";
            cell.Style.Font.Bold = true;
            cell.Style.Font.FontColor = column.Header.Contains('*') ? XLColor.FromHtml("#FFE7A3") : XLColor.White;
            cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
            cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
            cell.Style.Alignment.WrapText = true;
            sheet.Column(index + 1).Width = column.Width;
            sheet.Column(index + 1).Style.NumberFormat.Format = column.NumberFormat;
        }
        sheet.Row(2).Height = 25;
        IReadOnlyList<JsonObject> output = rows.Count > 0
            ? rows
            : template ? [new JsonObject(spec.Columns.ToDictionary(
                item => item.Key, item => ToNode(item.Example), StringComparer.Ordinal))] : [];
        for (var rowIndex = 0; rowIndex < output.Count; rowIndex++)
        {
            for (var columnIndex = 0; columnIndex < spec.Columns.Length; columnIndex++)
            {
                var value = output[rowIndex][spec.Columns[columnIndex].Key];
                SetValue(sheet.Cell(rowIndex + 3, columnIndex + 1), value);
                sheet.Cell(rowIndex + 3, columnIndex + 1).Style.Font.FontName = "微软雅黑";
                sheet.Cell(rowIndex + 3, columnIndex + 1).Style.Font.FontSize = 10;
                sheet.Cell(rowIndex + 3, columnIndex + 1).Style.Border.BottomBorder = XLBorderStyleValues.Hair;
                sheet.Cell(rowIndex + 3, columnIndex + 1).Style.Border.BottomBorderColor = XLColor.FromHtml("#D8E2E7");
                sheet.Cell(rowIndex + 3, columnIndex + 1).Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
            }
            sheet.Row(rowIndex + 3).Height = 23;
        }
        var lastRow = Math.Max(202, output.Count + 2);
        for (var index = 0; index < spec.Columns.Length; index++)
        {
            var validation = spec.Columns[index].Validation;
            if (validation is null) continue;
            var range = sheet.Range(3, index + 1, lastRow, index + 1);
            range.CreateDataValidation().List($"\"{string.Join(',', validation)}\"", true);
        }
        if (resource == "admission_reporting")
        {
            var status = Array.FindIndex(spec.Columns, item => item.Key == "reportingStatusCode") + 1;
            var note = Array.FindIndex(spec.Columns, item => item.Key == "reportingNote") + 1;
            sheet.Range(3, status, lastRow, status).Style.Fill.BackgroundColor = XLColor.FromHtml("#FFF3CD");
            sheet.Range(3, note, lastRow, note).Style.Fill.BackgroundColor = XLColor.FromHtml("#FFF3CD");
            title.Value = $"{spec.Title}｜仅修改黄色列；Y=已报到，N=未报到，P=待确认";
        }
        sheet.Range(2, 1, Math.Max(2, output.Count + 2), spec.Columns.Length).SetAutoFilter();
        using var stream = new MemoryStream();
        workbook.SaveAs(stream);
        return stream.ToArray();
    }

    public static IReadOnlyList<JsonObject> Parse(string resource, byte[] content)
    {
        var spec = Get(resource);
        XLWorkbook workbook;
        try
        {
            workbook = new XLWorkbook(new MemoryStream(content));
        }
        catch
        {
            throw new WorkbookException(400, "无法读取 Excel 文件，请使用系统下载的 .xlsx 模板");
        }
        using (workbook)
        {
            var sheet = workbook.Worksheets.FirstOrDefault(item => item.Name == spec.Sheet)
                        ?? workbook.Worksheets.FirstOrDefault()
                        ?? throw new WorkbookException(400, "Excel 文件中没有可读取的工作表");
            var headers = sheet.Row(2).CellsUsed().ToDictionary(
                item => NormalizeHeader(item.GetString()),
                item => item.Address.ColumnNumber,
                StringComparer.Ordinal);
            var missing = spec.Columns.Where(item => !headers.ContainsKey(NormalizeHeader(item.Header))).ToArray();
            if (missing.Length > 0)
                throw new WorkbookException(400, $"模板列不完整：缺少 {string.Join('、', missing.Select(item => item.Header))}");
            var rows = new List<JsonObject>();
            var lastRow = sheet.LastRowUsed()?.RowNumber() ?? 2;
            for (var rowNumber = 3; rowNumber <= lastRow; rowNumber++)
            {
                var output = new JsonObject { ["__row"] = rowNumber };
                var populated = false;
                foreach (var column in spec.Columns)
                {
                    var cell = sheet.Cell(rowNumber, headers[NormalizeHeader(column.Header)]);
                    var value = CellValue(cell);
                    output[column.Key] = value;
                    if (value is not null && value.ToString().Length > 0) populated = true;
                }
                if (populated) rows.Add(output);
            }
            if (rows.Count == 0) throw new WorkbookException(400, "Excel 中没有可导入的数据行");
            return rows;
        }
    }

    private static WorkbookSpec Get(string resource) =>
        Specs.GetValueOrDefault(resource) ?? throw new WorkbookException(404, "不支持的 Excel 数据类型");
    private static WorkbookColumn C(
        string key, string header, double width, object example,
        string numberFormat = "@", string[]? values = null) =>
        new(key, header, width, example, numberFormat, values);
    private static string NormalizeHeader(string value) => value.Replace("*", "", StringComparison.Ordinal).Trim();
    private static JsonNode? ToNode(object value) => value switch
    {
        int integer => JsonValue.Create(integer),
        double number => JsonValue.Create(number),
        bool boolean => JsonValue.Create(boolean),
        _ => JsonValue.Create(Convert.ToString(value, CultureInfo.InvariantCulture) ?? "")
    };
    private static JsonNode? CellValue(IXLCell cell)
    {
        if (cell.IsEmpty()) return JsonValue.Create("");
        if (cell.DataType == XLDataType.DateTime)
            return JsonValue.Create(cell.GetDateTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
        if (cell.DataType == XLDataType.Number) return JsonValue.Create(cell.GetDouble());
        if (cell.DataType == XLDataType.Boolean) return JsonValue.Create(cell.GetBoolean());
        return JsonValue.Create(cell.GetFormattedString().Trim());
    }
    private static void SetValue(IXLCell cell, JsonNode? value)
    {
        if (value is JsonValue json)
        {
            if (json.TryGetValue<double>(out var number)) { cell.Value = number; return; }
            if (json.TryGetValue<int>(out var integer)) { cell.Value = integer; return; }
            if (json.TryGetValue<bool>(out var boolean)) { cell.Value = boolean; return; }
        }
        cell.Value = value?.ToString() ?? "";
    }
}

internal sealed class WorkbookException(int statusCode, string message) : Exception(message)
{
    public int StatusCode { get; } = statusCode;
}
