using System.Data.Common;
using System.Text.Json.Nodes;
using Eis.Infrastructure.Security;

namespace Eis.Infrastructure.Public;

public sealed partial class PublicQueryService
{
    public async Task<JsonObject?> VerifyDocumentAsync(string code, CancellationToken cancellationToken)
    {
        var normalizedCode = (code ?? string.Empty).ToUpperInvariant();
        await using var connection = await connectionFactory.OpenAsync(cancellationToken);
        return normalizedCode.StartsWith("SR-", StringComparison.Ordinal)
            ? await VerifyScoreReportAsync(connection, normalizedCode, cancellationToken)
            : normalizedCode.StartsWith("AN-", StringComparison.Ordinal)
                ? await VerifyAdmissionNoticeAsync(connection, normalizedCode, cancellationToken)
                : null;
    }

    private async Task<JsonObject?> VerifyScoreReportAsync(
        DbConnection connection,
        string code,
        CancellationToken cancellationToken)
    {
        var registrations = await QueryAsync(connection,
            "SELECT id, user_id, exam_id, registration_number FROM registrations ORDER BY created_at, id",
            reader => new VerificationRegistrationRow(
                ReadString(reader, "id"), ReadString(reader, "user_id"), ReadString(reader, "exam_id"),
                ReadNullableString(reader, "registration_number") ?? string.Empty), cancellationToken);
        var results = await QueryAsync(connection,
            "SELECT registration_id, subject_id, score, updated_at, published_at FROM results WHERE published = 1 ORDER BY id",
            reader => new VerificationResultRow(
                ReadString(reader, "registration_id"), ReadString(reader, "subject_id"), ReadDouble(reader, "score"),
                ReadNullableString(reader, "updated_at"), ReadNullableString(reader, "published_at")), cancellationToken);
        var examNames = await ReadNamesAsync(connection, "exams", cancellationToken);
        var users = await QueryAsync(connection,
            "SELECT id, candidate_number, display_name FROM users ORDER BY id",
            reader => new VerificationUserRow(
                ReadString(reader, "id"), ReadNullableString(reader, "candidate_number") ?? string.Empty,
                ReadString(reader, "display_name")), cancellationToken);
        var profiles = await QueryAsync(connection,
            "SELECT user_id, name FROM candidate_profiles ORDER BY id",
            reader => new VerificationProfileRow(ReadString(reader, "user_id"), ReadString(reader, "name")), cancellationToken);
        var usersById = users.ToDictionary(item => item.Id, StringComparer.Ordinal);
        var profilesByUser = profiles.ToDictionary(item => item.UserId, StringComparer.Ordinal);

        foreach (var registration in registrations)
        {
            var reportResults = results.Where(item => item.RegistrationId == registration.Id).ToArray();
            if (reportResults.Length == 0 || !examNames.ContainsKey(registration.ExamId))
            {
                continue;
            }
            var expected = _documentCodes.ScoreReportCode(
                registration.Id, registration.UserId, registration.ExamId,
                reportResults.Select(item => new ScoreSignatureItem(item.SubjectId, item.Score, item.PublishedAt, item.UpdatedAt)));
            if (!DocumentVerificationCodeService.SafeEquals(code, expected))
            {
                continue;
            }

            usersById.TryGetValue(registration.UserId, out var user);
            profilesByUser.TryGetValue(registration.UserId, out var profile);
            var candidateNumber = user?.CandidateNumber ?? registration.RegistrationNumber;
            var latestResult = reportResults.OrderByDescending(item => ParseDate(item.PublishedAt ?? item.UpdatedAt)).First();
            return new JsonObject
            {
                ["ok"] = true,
                ["verified"] = true,
                ["document"] = new JsonObject
                {
                    ["type"] = "score-report",
                    ["typeName"] = "考生成绩单",
                    ["candidateName"] = HideName(profile?.Name ?? user?.DisplayName ?? string.Empty),
                    ["candidateNumber"] = MaskCandidateNumber(candidateNumber),
                    ["examName"] = examNames[registration.ExamId],
                    ["subjectCount"] = reportResults.Length,
                    ["totalScore"] = Math.Round(reportResults.Sum(item => item.Score), 2, MidpointRounding.AwayFromZero),
                    ["issuedAt"] = JsonValue.Create(latestResult.PublishedAt)
                }
            };
        }

        return null;
    }

    private async Task<JsonObject?> VerifyAdmissionNoticeAsync(
        DbConnection connection,
        string code,
        CancellationToken cancellationToken)
    {
        var placements = await QueryAsync(connection,
            "SELECT id, exam_id, user_id, school_id, payload_json, updated_at FROM admission_records WHERE kind = 'placement' AND status = 'final' ORDER BY created_at, id",
            reader =>
            {
                JsonObject payload;
                try
                {
                    payload = JsonNode.Parse(ReadString(reader, "payload_json"))?.AsObject() ?? new JsonObject();
                }
                catch
                {
                    payload = new JsonObject();
                }
                return new VerificationPlacementRow(
                    ReadString(reader, "id"), ReadString(reader, "exam_id"), ReadString(reader, "user_id"),
                    ReadString(reader, "school_id"), payload, ReadString(reader, "updated_at"));
            }, cancellationToken);
        var examNames = await ReadNamesAsync(connection, "exams", cancellationToken);
        var schoolNames = await ReadNamesAsync(connection, "schools", cancellationToken);
        var profiles = await QueryAsync(connection,
            "SELECT user_id, name FROM candidate_profiles ORDER BY id",
            reader => new VerificationProfileRow(ReadString(reader, "user_id"), ReadString(reader, "name")), cancellationToken);
        var profilesByUser = profiles.ToDictionary(item => item.UserId, StringComparer.Ordinal);

        foreach (var placement in placements)
        {
            if (!examNames.ContainsKey(placement.ExamId))
            {
                continue;
            }
            var expected = _documentCodes.AdmissionNoticeCode(
                placement.Id, placement.UserId, placement.SchoolId, placement.ExamId,
                GetString(placement.Payload, "categoryCode") ?? string.Empty,
                GetString(placement.Payload, "noticeNumber") ?? string.Empty,
                placement.UpdatedAt);
            if (!DocumentVerificationCodeService.SafeEquals(code, expected))
            {
                continue;
            }

            profilesByUser.TryGetValue(placement.UserId, out var profile);
            return new JsonObject
            {
                ["ok"] = true,
                ["verified"] = true,
                ["document"] = new JsonObject
                {
                    ["type"] = "admission-notice",
                    ["typeName"] = "录取通知书",
                    ["noticeNumber"] = GetString(placement.Payload, "noticeNumber") ?? string.Empty,
                    ["candidateName"] = HideName(profile?.Name ?? string.Empty),
                    ["examName"] = examNames[placement.ExamId],
                    ["schoolName"] = schoolNames.GetValueOrDefault(placement.SchoolId) ?? string.Empty,
                    ["categoryName"] = GetString(placement.Payload, "categoryName") ?? string.Empty,
                    ["issuedAt"] = placement.UpdatedAt
                }
            };
        }

        return null;
    }

    private static async Task<Dictionary<string, string>> ReadNamesAsync(
        DbConnection connection,
        string table,
        CancellationToken cancellationToken)
    {
        var allowedTable = table switch
        {
            "exams" => "exams",
            "schools" => "schools",
            _ => throw new ArgumentOutOfRangeException(nameof(table))
        };
        var rows = await QueryAsync(connection, $"SELECT id, name FROM {allowedTable} ORDER BY id",
            reader => new KeyValuePair<string, string>(ReadString(reader, "id"), ReadString(reader, "name")), cancellationToken);
        return rows.ToDictionary(item => item.Key, item => item.Value, StringComparer.Ordinal);
    }

    private static string HideName(string value) => value.Length == 0
        ? string.Empty
        : $"{value[..1]}{new string('*', Math.Max(1, value.Length - 1))}";

    private static string MaskCandidateNumber(string value) => value.Length > 6
        ? $"{value[..3]}****{value[^3..]}"
        : value;

    private sealed record VerificationRegistrationRow(string Id, string UserId, string ExamId, string RegistrationNumber);
    private sealed record VerificationResultRow(string RegistrationId, string SubjectId, double Score, string? UpdatedAt, string? PublishedAt);
    private sealed record VerificationUserRow(string Id, string CandidateNumber, string DisplayName);
    private sealed record VerificationProfileRow(string UserId, string Name);
    private sealed record VerificationPlacementRow(string Id, string ExamId, string UserId, string SchoolId, JsonObject Payload, string UpdatedAt);
}
