using System.Globalization;
using System.Net;
using System.Text;
using System.Text.Json.Nodes;
using Eis.Infrastructure.Authentication;

namespace Eis.Infrastructure.Candidate;

internal static class CandidateAdmitCardRenderer
{
    private const string Styles = "@page{size:A4 landscape;margin:10mm}*{box-sizing:border-box}body{font-family:\"Microsoft YaHei\",sans-serif;color:#182552;margin:0;background:#f2f4f8}.card{max-width:1120px;min-height:720px;margin:24px auto;background:#fff;border:2px solid #182552;padding:26px;box-shadow:0 12px 35px #18255218;break-after:page}.card:last-child{break-after:auto}.head{display:flex;justify-content:space-between;align-items:flex-start;border-bottom:3px double #182552;padding-bottom:14px}.head>div:last-child{text-align:right}.head small{color:#64748b}.brand{font-size:27px;font-weight:800}.head p{margin:6px 0}.code{font:700 22px Consolas,monospace;margin-top:5px}.meta{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:16px 0}.meta div,.center>div{border:1px solid #d9e0e7;padding:9px 12px}.meta span,.center span{display:block;color:#64748b;font-size:11px;margin-bottom:4px}.center{display:grid;grid-template-columns:1fr 1.5fr;gap:10px;margin-bottom:16px}.center small,td small{display:block;color:#64748b;margin-top:4px}table{width:100%;border-collapse:collapse;font-size:12px}th,td{border:1px solid #b9c5d2;padding:9px;text-align:left;vertical-align:middle}th{background:#eaf1f5}.room-no{font:700 15px Consolas,monospace;text-align:center}.explain,.notice{margin-top:12px;padding:10px 12px;font-size:12px}.explain{background:#eaf4f6;color:#245365}.explain strong{margin-right:8px}.notice{background:#fff7e6;color:#704b12}.footer{display:flex;justify-content:space-between;margin-top:14px;color:#64748b;font-size:11px}@media print{body{background:#fff}.card{margin:0;box-shadow:none}}";

    public static string Render(
        CandidateReadSnapshot snapshot,
        AuthenticationUser user,
        JsonObject profile,
        CandidateRegistration registration,
        CandidateExam exam)
    {
        var card = registration.AdmitCard!;
        var assignments = card.Assignments.ToDictionary(item => item.SubjectId, StringComparer.Ordinal);
        var rows = new StringBuilder();
        foreach (var subject in exam.Subjects.Where(item => registration.SubjectIds.Contains(item.Id, StringComparer.Ordinal)))
        {
            assignments.TryGetValue(subject.Id, out var assignment);
            var room = assignment?.RoomId is not null
                ? snapshot.TestRooms.GetValueOrDefault(assignment.RoomId)
                : null;
            rows.Append("<tr><td>").Append(E(subject.Name))
                .Append("</td><td>").Append(E(subject.Date))
                .Append("</td><td>").Append(E(subject.Start)).Append('—').Append(E(subject.End))
                .Append("</td><td class=\"room-no\">").Append(E(First(assignment?.ExamRoomCode, "待定")))
                .Append("</td><td><strong>").Append(E(First(assignment?.RoomName, room?.Name, "待定")))
                .Append("</strong><small>场地代码：").Append(E(First(assignment?.RoomCode, room?.Code, "—")))
                .Append("</small></td><td>").Append(E(First(assignment?.Building, room?.Building, "—")))
                .Append("<small>").Append(E(First(assignment?.Floor, room?.Floor, "楼层待定")))
                .Append("</small></td><td>").Append(E(First(assignment?.Seat, "—"))).Append("</td></tr>");
        }

        var center = card.CenterId is not null ? snapshot.TestCenters.GetValueOrDefault(card.CenterId) : null;
        var centerCode = First(card.CenterCode, center?.Code, string.Empty);
        var centerAddress = First(
            card.CenterAddress,
            center is null ? string.Empty : string.Join(' ', new[] { center.ProvinceName, center.CityName, center.DistrictName, center.Address }.Where(value => value.Length > 0)),
            string.Empty);
        var profileName = Text(profile, "name");
        var title = $"{exam.Name}-{profileName}-准考证";
        var generatedAt = DateTimeOffset.TryParse(card.GeneratedAt, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed)
            ? parsed.ToLocalTime().ToString("yyyy/M/d HH:mm:ss", CultureInfo.GetCultureInfo("zh-CN"))
            : card.GeneratedAt;
        var section = $"<main class=\"card\"><header class=\"head\"><div><div class=\"brand\">衡准 · 准考证</div><p>{E(exam.Name)}</p></div><div><small>准考证号</small><div class=\"code\">{E(card.Number)}</div></div></header><section class=\"meta\"><div><span>姓名</span><strong>{E(First(profileName, user.DisplayName))}</strong></div><div><span>证件号码</span><strong>{E(MaskId(Text(profile, "idNumber")))}</strong></div><div><span>报名号</span><strong>{E(First(user.CandidateNumber, registration.RegistrationNumber, "—"))}</strong></div></section><section class=\"center\"><div><span>固定考点</span><strong>{E(card.TestCenter)}</strong><small>考点代码：{E(First(centerCode, "—"))}</small></div><div><span>考点详细地址</span><strong>{E(First(centerAddress, "地址待公布"))}</strong></div></section><table><thead><tr><th>科目</th><th>日期</th><th>时间</th><th>考试考场序号</th><th>考场通用名称 / 场地代码</th><th>楼栋 / 楼层</th><th>座位号</th></tr></thead><tbody>{rows}</tbody></table><div class=\"explain\"><strong>编号说明</strong>“考试考场序号”是本次考试编排编号；“考场通用名称 / 场地代码”是考点内的物理场地档案，两者不是同一概念。</div><div class=\"notice\">所有科目均安排在同一考点，但不同科目可能对应不同考试考场序号、物理场地和座位。请逐科核对，并携带本人有效身份证件及本准考证至少提前 40 分钟到达考点。</div><footer class=\"footer\"><span>{E(snapshot.OrganizationName)}</span><span>生成时间：{E(generatedAt)}</span></footer></main>";
        return $"<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"><title>{E(title)}</title><style>{Styles}</style></head><body>{section}</body></html>";
    }

    private static string Text(JsonObject value, string property) => value[property]?.ToString() ?? string.Empty;

    private static string MaskId(string value) => value.Length > 8 ? $"{value[..4]}********{value[^4..]}" : value;

    private static string First(params string?[] values) => values.FirstOrDefault(value => !string.IsNullOrEmpty(value)) ?? string.Empty;

    private static string E(string? value) => WebUtility.HtmlEncode(value ?? string.Empty);
}
