using System.Globalization;
using System.Security.Cryptography;
using System.Text.Json.Nodes;
using Eis.Application.Candidate;

namespace Eis.Infrastructure.Candidate;

internal sealed partial class CandidateService
{
    private static readonly IReadOnlyDictionary<string, string> AdminLevelNames =
        new Dictionary<string, string>(StringComparer.Ordinal)
        {
            ["super"] = "超级管理员",
            ["school"] = "校级管理员",
            ["class"] = "班级管理员"
        };

    private static readonly IReadOnlyDictionary<string, string[]> SpecialtyTypes =
        new Dictionary<string, string[]>(StringComparer.Ordinal)
        {
            ["sports"] =
            [
                "track_field", "basketball", "football", "volleyball", "table_tennis", "badminton",
                "swimming", "martial_arts", "aerobics_cheer"
            ],
            ["arts"] =
            [
                "vocal_music", "instrumental_music", "dance", "fine_arts", "calligraphy", "drama_broadcasting"
            ]
        };

    public async Task<CandidateEndpointResult> UpdateProfileAsync(
        string sessionToken,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, profileRoute: true, cancellationToken);
        if (context.Error is not null)
        {
            return context.Error;
        }

        var user = context.User!;
        var profile = context.Profile!;
        foreach (var field in new[]
                 {
                     "name", "gender", "idNumber", "phone", "email", "address", "emergencyContact",
                     "emergencyPhone", "nativePlace", "birthDate", "ethnicity", "postalCode", "guardianName",
                     "guardianPhone", "specialtyCertificate", "policyEligibility"
                 })
        {
            profile[field] = Clean(body, field, field == "address" ? 160 : 80);
        }

        var specialtyCategory = Clean(body, "specialtyCategory", 30);
        var specialtyType = Clean(body, "specialtyType", 40);
        if (!ValidSpecialty(specialtyCategory, specialtyType))
        {
            return Error(400, "请选择对应的特长生大类和小类");
        }

        profile["specialtyCategory"] = specialtyCategory;
        profile["specialtyType"] = specialtyType;
        profile["specialtyTypes"] = specialtyType.Length == 0
            ? new JsonArray()
            : new JsonArray(JsonValue.Create(specialtyType));

        var region = regionCatalog.Resolve(
            Text(body, "provinceCode"),
            Text(body, "cityCode"),
            Text(body, "districtCode"));
        if (region is null)
        {
            return Error(400, "请选择有效的省、市和区县");
        }

        profile["provinceCode"] = region.ProvinceCode;
        profile["provinceName"] = region.ProvinceName;
        profile["cityCode"] = region.CityCode;
        profile["cityName"] = region.CityName;
        profile["districtCode"] = region.DistrictCode;
        profile["districtName"] = region.DistrictName;

        var school = await writeRepository.FindSchoolClassAsync(
            Clean(body, "schoolId", 64),
            Clean(body, "classId", 64),
            cancellationToken);
        if (school is null)
        {
            return Error(400, "请选择有效的学校和班级");
        }

        profile["schoolId"] = school.SchoolId;
        profile["classId"] = school.ClassId;
        profile["school"] = school.SchoolName;
        profile["grade"] = school.ClassName;
        var name = Text(profile, "name");
        var gender = Text(profile, "gender");
        var idNumber = Text(profile, "idNumber");
        if (name.Length == 0 || gender is not ("男" or "女") || idNumber.Length == 0 ||
            idNumber.StartsWith("PENDING-", StringComparison.Ordinal) ||
            Text(profile, "nativePlace").Length == 0 || Text(profile, "address").Length == 0 ||
            Text(profile, "phone").Length == 0 || Text(profile, "email").Length == 0 ||
            Text(profile, "school").Length == 0 || Text(profile, "classId").Length == 0)
        {
            return Error(400, "请完整填写姓名、性别、证件号码、籍贯、省市区县、家庭住址、手机号、邮箱、学校和班级");
        }

        if (await writeRepository.IdNumberExistsAsync(idNumber, ProfileId(profile), cancellationToken))
        {
            return Error(409, "证件号码已被其他考生使用");
        }

        profile["status"] = "pending";
        profile["profileCompleted"] = true;
        profile["reviewNote"] = string.Empty;
        profile["updatedAt"] = NowIso();
        var snapshot = await snapshotLoader.LoadAsync(user.Id, cancellationToken);
        WorkflowSubmission? submission = null;
        if (FindInstance(snapshot, "profile_change", ProfileId(profile)) is not { Status: "pending" })
        {
            var workflowResult = CreateWorkflowSubmission(
                snapshot,
                "profile_change",
                ProfileId(profile),
                profile,
                user.Id);
            if (workflowResult.Error is not null)
            {
                return workflowResult.Error;
            }
            submission = workflowResult.Submission;
        }

        await writeRepository.UpdateProfileAsync(
            profile,
            name,
            submission?.Instance,
            submission?.Action,
            cancellationToken);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["profile"] = profile,
            ["message"] = "资料已提交，等待管理员复核"
        });
    }

    public async Task<CandidateEndpointResult> CreateRegistrationAsync(
        string sessionToken,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, profileRoute: false, cancellationToken);
        if (context.Error is not null)
        {
            return context.Error;
        }

        var user = context.User!;
        var profile = context.Profile!;
        if (Text(profile, "status") != "approved")
        {
            return Error(403, "个人资料审核通过后才能报名考试");
        }

        var snapshot = await snapshotLoader.LoadAsync(user.Id, cancellationToken);
        var examId = Text(body, "examId");
        var exam = snapshot.Exams.FirstOrDefault(item =>
            item.Id == examId && item.Status == "published" && item.ArchivedAt is null);
        if (exam is null)
        {
            return Error(404, "考试不存在或尚未发布");
        }

        var registrationState = RegistrationState(exam);
        if (registrationState != "open")
        {
            return Error(400, registrationState == "upcoming" ? "报名尚未开始" : "报名已经截止");
        }

        if (snapshot.Registrations.Any(item => item.ExamId == exam.Id) ||
            await writeRepository.RegistrationExistsAsync(user.Id, exam.Id, cancellationToken))
        {
            return Error(409, "你已经报名该考试");
        }

        var subjectIds = body["subjectIds"] is JsonArray array
            ? array.Select(item => item?.ToString() ?? string.Empty)
                .Distinct(StringComparer.Ordinal)
                .ToArray()
            : [];
        if (subjectIds.Length == 0 || subjectIds.Any(id => exam.Subjects.All(subject => subject.Id != id)))
        {
            return Error(400, "请选择有效的报考科目");
        }

        var now = NowIso();
        var registration = new CandidateRegistration(
            Uid("reg"),
            user.Id,
            exam.Id,
            subjectIds,
            "pending",
            "unpaid",
            null,
            null,
            now,
            null,
            string.Empty,
            user.CandidateNumber ?? string.Empty,
            await writeRepository.GetActiveNumberRuleIdAsync(cancellationToken),
            0,
            null);
        var workflowResult = CreateWorkflowSubmission(
            snapshot,
            "registration_review",
            registration.Id,
            profile,
            user.Id);
        if (workflowResult.Error is not null)
        {
            return workflowResult.Error;
        }

        var submission = workflowResult.Submission!;
        await writeRepository.CreateRegistrationAsync(
            registration,
            submission.Instance,
            submission.Action,
            cancellationToken);
        return new CandidateEndpointResult(
            201,
            new JsonObject
            {
                ["ok"] = true,
                ["registration"] = RegistrationView(snapshot, registration),
                ["message"] = "考试报名已提交"
            });
    }

    private static WorkflowSubmissionResult CreateWorkflowSubmission(
        CandidateReadSnapshot snapshot,
        string businessType,
        string businessId,
        JsonObject profile,
        string actorId)
    {
        var workflow = snapshot.Workflows.FirstOrDefault(item => item.BusinessType == businessType && item.Active);
        if (workflow is null || workflow.Steps.Count == 0)
        {
            return WorkflowSubmissionResult.Failed(Error(409, "该业务尚未配置审批流程"));
        }

        var firstStep = workflow.Steps[0];
        var schoolId = Text(profile, "schoolId");
        var classId = Text(profile, "classId");
        var pendingByAdmin = snapshot.WorkflowInstances
            .Where(item => item.Status == "pending" && item.AssigneeId is not null)
            .GroupBy(item => item.AssigneeId!, StringComparer.Ordinal)
            .ToDictionary(group => group.Key, group => group.Count(), StringComparer.Ordinal);
        var assignedByAdmin = snapshot.WorkflowActions
            .Where(item => item.ToAssigneeId is not null)
            .GroupBy(item => item.ToAssigneeId!, StringComparer.Ordinal)
            .ToDictionary(group => group.Key, group => group.Count(), StringComparer.Ordinal);
        var assignee = snapshot.Users.Values
            .Where(item => item.Role == "admin" && item.Active && (item.AdminLevel ?? "super") == firstStep.AdminLevel)
            .Where(item => firstStep.AdminLevel switch
            {
                "super" => true,
                "school" => schoolId.Length > 0 && item.SchoolId == schoolId,
                "class" => schoolId.Length > 0 && classId.Length > 0 && item.SchoolId == schoolId && item.ClassId == classId,
                _ => false
            })
            .OrderBy(item => pendingByAdmin.GetValueOrDefault(item.Id))
            .ThenBy(item => assignedByAdmin.GetValueOrDefault(item.Id))
            .ThenBy(item => item.CreatedAt, StringComparer.Ordinal)
            .ThenBy(item => item.Id, StringComparer.Ordinal)
            .FirstOrDefault();
        if (assignee is null)
        {
            var levelName = AdminLevelNames.GetValueOrDefault(firstStep.AdminLevel) ?? firstStep.AdminLevel;
            return WorkflowSubmissionResult.Failed(Error(409, $"没有可承接“{firstStep.Name}”的{levelName}"));
        }

        var instance = new CandidateWorkflowInstance(
            Uid("flow"),
            workflow.Id,
            businessType,
            businessId,
            "pending",
            1,
            assignee.Id,
            NowIso(),
            null);
        var action = new CandidateWorkflowAction(
            Uid("flow_action"),
            instance.Id,
            actorId,
            "submit",
            "提交审批",
            null,
            assignee.Id,
            NowIso());
        return new WorkflowSubmissionResult(new WorkflowSubmission(instance, action), null);
    }

    private static bool ValidSpecialty(string category, string type)
    {
        if (category.Length == 0 && type.Length == 0)
        {
            return true;
        }

        return SpecialtyTypes.TryGetValue(category, out var types) && types.Contains(type, StringComparer.Ordinal);
    }

    private static string RegistrationState(CandidateExam exam)
    {
        var now = DateTimeOffset.UtcNow;
        if (DateTimeOffset.TryParse(exam.RegistrationStart, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var start) && now < start)
        {
            return "upcoming";
        }

        if (DateTimeOffset.TryParse(exam.RegistrationEnd, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var end) && now > end)
        {
            return "closed";
        }

        return "open";
    }

    private static string Clean(JsonObject value, string property, int maximum)
    {
        var normalized = Text(value, property).Trim();
        return normalized[..Math.Min(normalized.Length, maximum)];
    }

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

    private static string NowIso() => DateTimeOffset.UtcNow.ToString(
        "yyyy-MM-dd'T'HH:mm:ss.fff'Z'",
        CultureInfo.InvariantCulture);

    private static string Uid(string prefix) =>
        $"{prefix}_{ToBase36(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())}_{Convert.ToHexStringLower(RandomNumberGenerator.GetBytes(4))}";

    private static string ToBase36(long value)
    {
        const string alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
        Span<char> buffer = stackalloc char[16];
        var position = buffer.Length;
        do
        {
            buffer[--position] = alphabet[(int)(value % 36)];
            value /= 36;
        }
        while (value > 0);
        return new string(buffer[position..]);
    }

    private sealed record WorkflowSubmission(
        CandidateWorkflowInstance Instance,
        CandidateWorkflowAction Action);

    private sealed record WorkflowSubmissionResult(
        WorkflowSubmission? Submission,
        CandidateEndpointResult? Error)
    {
        public static WorkflowSubmissionResult Failed(CandidateEndpointResult error) => new(null, error);
    }
}
