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

namespace Eis.Infrastructure.Administration;

internal sealed record AdminPublicationRecord(
    string Id,
    string Kind,
    string ExamId,
    string? UserId,
    string? SchoolId,
    string Status,
    JsonObject Payload,
    string CreatedAt,
    string UpdatedAt);

internal sealed record AdminNoticeManagementSnapshot(
    IReadOnlyList<AdminNotice> Notices,
    IReadOnlyList<AdminPublicationRecord> Records,
    IReadOnlyDictionary<string, string> ExamNames,
    IReadOnlyDictionary<string, string> SchoolNames);

internal static class AdminPublicationProjector
{
    private static readonly HashSet<string> AdmissionRoundStatuses = ["reporting", "supplementary", "completed"];

    public static AdminPublicationRecord? FindSourceRecord(
        IEnumerable<AdminPublicationRecord> records,
        string sourceType,
        string publicationId) => records.FirstOrDefault(item => item.Id == publicationId && sourceType switch
        {
            "plan" => item.Kind == "plan",
            "qualification" => item.Kind == "qualification_publication",
            "admission" => item.Kind == "setting" ||
                           (item.Kind == "notification" && Text(item.Payload, "type") == "admission_round_publication"),
            "cutoff" => item.Kind == "cutoff_publication",
            "reporting" => item.Kind == "notification" && Text(item.Payload, "type") == "admission_reporting",
            _ => false
        });

    public static IReadOnlyList<JsonObject> Build(AdminNoticeManagementSnapshot snapshot)
    {
        var records = snapshot.Records;
        var settings = records.Where(item => item.Kind == "setting")
            .GroupBy(item => item.ExamId)
            .ToDictionary(group => group.Key, group => group.First(), StringComparer.Ordinal);
        var publications = new List<JsonObject>();

        publications.AddRange(records
            .Where(item => item.Kind == "plan" && item.Status == "approved")
            .Select(item => View(
                item.Id,
                "plan",
                "招生计划",
                $"{ExamName(snapshot, item.ExamId)} · {SchoolName(snapshot, item.SchoolId)}招生计划公示",
                Text(item.Payload, "reviewedAt") ?? item.UpdatedAt,
                "审核通过后由系统生成，当前页面仅控制是否在公开通知目录显示。",
                item.Payload)));

        publications.AddRange(records
            .Where(item => item.Kind == "qualification_publication" && item.Status == "published")
            .Select(item => View(
                item.Id,
                "qualification",
                "指标资格",
                $"{ExamName(snapshot, item.ExamId)} · {SchoolName(snapshot, item.SchoolId)}指标分配资格公示",
                Text(item.Payload, "publishedAt") ?? item.UpdatedAt,
                "资格确认完成后由系统生成，内容随资格确认结果更新。",
                item.Payload)));

        var rounds = BuildRounds(records);
        publications.AddRange(rounds
            .Where(item => !settings.TryGetValue(item.ExamId, out var setting) || Boolean(setting.Payload, "autoPublish", true))
            .Select(item => View(
                item.SourceRecordId,
                "admission",
                "录取名单",
                $"{ExamName(snapshot, item.ExamId)}第 {item.Round} 轮录取名单公示",
                item.PublishedAt,
                $"第 {item.Round} 轮录取通知书签发后由系统自动生成，共 {item.RowCount} 人。",
                item.Payload)));

        var virtualSourceIds = rounds.Where(item => item.Virtual)
            .Select(item => item.SourceRecordId)
            .ToHashSet(StringComparer.Ordinal);
        publications.AddRange(records
            .Where(item => item.Kind == "setting" && item.Status == "completed" &&
                           Boolean(item.Payload, "autoPublish", true) && !virtualSourceIds.Contains(item.Id))
            .Select(item => View(
                item.Id,
                "admission",
                "录取名单",
                $"{ExamName(snapshot, item.ExamId)}最终录取名单",
                Text(item.Payload, "completedAt") ?? item.UpdatedAt,
                "录取结束后由系统生成，内容取自最终录取结果。",
                item.Payload)));

        publications.AddRange(records
            .Where(item => item.Kind == "cutoff_publication" && item.Status == "published" &&
                           (!settings.TryGetValue(item.ExamId, out var setting) || Boolean(setting.Payload, "autoPublish", true)))
            .Select(item => View(
                item.Id,
                "cutoff",
                "录取分数线",
                $"{ExamName(snapshot, item.ExamId)}录取分数线",
                Text(item.Payload, "publishedAt") ?? item.UpdatedAt,
                "录取结束后由系统生成，内容取自各招生类别最低录取分数。",
                item.Payload)));

        publications.AddRange(records
            .Where(item => item.Kind == "notification" && item.UserId is null && item.Status == "approved" &&
                           Text(item.Payload, "type") == "admission_reporting")
            .Select(item => ReportingView(snapshot, item)));

        return publications
            .OrderByDescending(item => ParseDate(item["publishedAt"]?.GetValue<string>()))
            .ToArray();
    }

    private static IReadOnlyList<RoundPublication> BuildRounds(IReadOnlyList<AdminPublicationRecord> records)
    {
        var rounds = records
            .Where(item => item.Kind == "notification" && item.UserId is null && item.Status == "published" &&
                           Text(item.Payload, "type") == "admission_round_publication")
            .Select(item => new RoundPublication(
                item.Id,
                item.ExamId,
                Math.Max(1, (int)Number(item.Payload, "round", 1)),
                Text(item.Payload, "publishedAt") ?? item.UpdatedAt,
                Array(item.Payload, "rows").Count,
                item.Payload,
                false))
            .ToList();
        var keys = rounds.Select(item => $"{item.ExamId}:{item.Round}").ToHashSet(StringComparer.Ordinal);

        foreach (var setting in records.Where(item => item.Kind == "setting" && AdmissionRoundStatuses.Contains(item.Status)))
        {
            var finalizedRounds = records
                .Where(item => item.Kind == "placement" && item.ExamId == setting.ExamId && item.Status is "final" or "forfeited")
                .Select(item => Math.Max(1, (int)Number(item.Payload, "finalizedRound", 1)))
                .ToArray();
            var round = finalizedRounds.Length == 0 ? 0 : finalizedRounds.Max();
            if (round == 0 || keys.Contains($"{setting.ExamId}:{round}")) continue;
            var rowCount = records.Count(item => item.Kind == "placement" && item.ExamId == setting.ExamId &&
                                                  (item.Status is "final" or "forfeited") &&
                                                  Math.Max(1, (int)Number(item.Payload, "finalizedRound", 1)) == round);
            var payload = new JsonObject
            {
                ["publicVisible"] = Boolean(setting.Payload, "publicVisible", true)
            };
            rounds.Add(new RoundPublication(
                setting.Id,
                setting.ExamId,
                round,
                Text(setting.Payload, "roundPublishedAt") ?? setting.UpdatedAt,
                rowCount,
                payload,
                true));
        }
        return rounds;
    }

    private static JsonObject ReportingView(AdminNoticeManagementSnapshot snapshot, AdminPublicationRecord item)
    {
        var statistics = item.Payload["statistics"] as JsonObject ?? new JsonObject();
        var supplement = Text(item.Payload, "supplementDecision") == "supplement";
        var examName = ExamName(snapshot, item.ExamId);
        var schoolName = SchoolName(snapshot, item.SchoolId);
        var summary = $"计划 {Format(Number(statistics, "totalQuota"))} 人，已报到 {Format(Number(statistics, "reportedCount"))} 人，完成率 {Format(Number(statistics, "reportingRate"))}%。";
        return View(
            item.Id,
            "reporting",
            "考生报到",
            supplement ? $"{examName} · {schoolName}考生报到情况及补录说明" : $"{examName} · {schoolName}考生报到情况公示",
            Text(item.Payload, "approvedAt") ?? item.UpdatedAt,
            summary,
            item.Payload);
    }

    private static JsonObject View(
        string id,
        string sourceType,
        string category,
        string title,
        string publishedAt,
        string summary,
        JsonObject payload)
    {
        var visible = Boolean(payload, "publicVisible", true);
        return new JsonObject
        {
            ["id"] = id,
            ["sourceType"] = sourceType,
            ["category"] = category,
            ["title"] = title,
            ["summary"] = summary,
            ["author"] = "系统自动发布",
            ["publishedAt"] = publishedAt,
            ["visible"] = visible,
            ["status"] = visible ? "visible" : "hidden"
        };
    }

    private static string ExamName(AdminNoticeManagementSnapshot snapshot, string examId) =>
        snapshot.ExamNames.GetValueOrDefault(examId) ?? "未知考试";

    private static string SchoolName(AdminNoticeManagementSnapshot snapshot, string? schoolId) =>
        schoolId is not null ? snapshot.SchoolNames.GetValueOrDefault(schoolId) ?? "未知学校" : "未知学校";

    private static string? Text(JsonObject payload, string name) =>
        payload[name] is JsonValue value && value.TryGetValue<string>(out var text) ? text : null;

    private static bool Boolean(JsonObject payload, string name, bool fallback) =>
        payload[name] is JsonValue value && value.TryGetValue<bool>(out var result) ? result : fallback;

    private static double Number(JsonObject payload, string name, double fallback = 0)
    {
        if (payload[name] is not JsonValue value) return fallback;
        if (value.TryGetValue<double>(out var number)) return number;
        if (value.TryGetValue<int>(out var integer32)) return integer32;
        if (value.TryGetValue<long>(out var integer)) return integer;
        if (value.TryGetValue<decimal>(out var decimalNumber)) return (double)decimalNumber;
        if (value.TryGetValue<string>(out var text) && double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out number)) return number;
        return fallback;
    }

    private static JsonArray Array(JsonObject payload, string name) => payload[name] as JsonArray ?? [];
    private static string Format(double number) => number.ToString("0.################", CultureInfo.InvariantCulture);
    private static DateTimeOffset ParseDate(string? value) =>
        DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed) ? parsed : DateTimeOffset.MinValue;

    private sealed record RoundPublication(
        string SourceRecordId,
        string ExamId,
        int Round,
        string PublishedAt,
        int RowCount,
        JsonObject Payload,
        bool Virtual);
}
