using System.Globalization;
using System.Text.Json.Nodes;
using Eis.Application.Administration;
using Eis.Infrastructure.Authentication;

namespace Eis.Infrastructure.Administration;

internal sealed class AdminOperationalReadService(
    IAuthenticationStateStore authenticationState,
    AuthenticationRepository authenticationRepository,
    AdminAccountBatchSnapshotLoader workflowSnapshotLoader,
    AdminOperationalSnapshotLoader operationalSnapshotLoader) : IAdminOperationalReadService
{
    public Task<AdminEndpointResult> GetCandidatesAsync(string sessionToken, CancellationToken cancellationToken) =>
        ExecuteAsync(sessionToken, "candidates", cancellationToken);

    public Task<AdminEndpointResult> GetRegistrationsAsync(string sessionToken, CancellationToken cancellationToken) =>
        ExecuteAsync(sessionToken, "registrations", cancellationToken);

    public Task<AdminEndpointResult> GetPaymentsAsync(string sessionToken, CancellationToken cancellationToken) =>
        ExecuteAsync(sessionToken, "payments", cancellationToken);

    private async Task<AdminEndpointResult> ExecuteAsync(
        string sessionToken,
        string resource,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var workflow = await workflowSnapshotLoader.LoadAsync(cancellationToken);
        var operational = await operationalSnapshotLoader.LoadAsync(cancellationToken);
        return resource switch
        {
            "candidates" => Candidates(user, workflow, operational),
            "registrations" => Registrations(user, workflow, operational),
            _ => Payments(user, workflow, operational)
        };
    }

    private static AdminEndpointResult Candidates(
        AuthenticationUser user,
        AdminAccountBatchSnapshot workflow,
        AdminOperationalSnapshot operational)
    {
        var candidates = operational.Profiles.Where(profile => InScope(user, profile)).Select(profile =>
        {
            var output = profile.Data.DeepClone().AsObject();
            var account = operational.Users.FirstOrDefault(item => item.Id == profile.UserId);
            var idNumber = output["idNumber"]?.GetValue<string>() ?? "";
            output["idNumberMasked"] = idNumber.StartsWith("PENDING-", StringComparison.Ordinal) ? "待考生补充" : MaskId(idNumber);
            if (account is not null) output["username"] = account.Username;
            output["candidateNumber"] = account?.CandidateNumber ?? "";
            output["mustChangePassword"] = account?.MustChangePassword ?? false;
            output["accountArchived"] = account?.ArchivedAt is not null;
            output["archivedAt"] = JsonValue.Create(account?.ArchivedAt);
            output["archivedByName"] = operational.Users.FirstOrDefault(item => item.Id == account?.ArchivedBy)?.DisplayName ?? "";
            var registrations = operational.Registrations.Where(item => item.UserId == profile.UserId)
                .OrderByDescending(item => ParseDate(item.CreatedAt))
                .Select(item => RegistrationJson(workflow, operational, item)).ToArray();
            output["registrations"] = new JsonArray(registrations);
            var instance = WorkflowInstance(workflow, "profile_change", profile.Id);
            output["workflow"] = instance is null ? null : WorkflowJson(workflow, instance);
            return output;
        }).ToArray();
        var schools = workflow.Schools.Where(item => item.Active && (Level(user) == "super" || item.Id == user.SchoolId))
            .Select(SchoolJson).ToArray();
        var classes = workflow.Classes.Where(item => item.Active && (Level(user) == "super" || item.SchoolId == user.SchoolId))
            .Select(ClassJson).ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["candidates"] = new JsonArray(candidates),
            ["schools"] = new JsonArray(schools),
            ["classes"] = new JsonArray(classes)
        });
    }

    private static AdminEndpointResult Registrations(
        AuthenticationUser user,
        AdminAccountBatchSnapshot workflow,
        AdminOperationalSnapshot operational)
    {
        var registrations = operational.Registrations.Select(registration =>
        {
            var profile = operational.Profiles.FirstOrDefault(item => item.UserId == registration.UserId);
            if (profile is null || !InScope(user, profile)) return null;
            var output = RegistrationJson(workflow, operational, registration);
            var candidate = profile.Data.DeepClone().AsObject();
            candidate["idNumber"] = MaskId(candidate["idNumber"]?.GetValue<string>() ?? "");
            output["candidate"] = candidate;
            var schoolClass = workflow.Classes.FirstOrDefault(item => item.Id == profile.ClassId);
            output["schoolName"] = workflow.Schools.FirstOrDefault(item => item.Id == profile.SchoolId)?.Name ??
                                   profile.Data["school"]?.GetValue<string>() ?? "";
            output["gradeName"] = schoolClass?.Grade ?? "";
            output["className"] = schoolClass?.Name ?? profile.Data["grade"]?.GetValue<string>() ?? "";
            return output;
        }).Where(item => item is not null).Cast<JsonObject>().ToArray();
        return Success(new JsonObject { ["ok"] = true, ["registrations"] = new JsonArray(registrations) });
    }

    private static AdminEndpointResult Payments(
        AuthenticationUser user,
        AdminAccountBatchSnapshot workflow,
        AdminOperationalSnapshot operational)
    {
        var registrations = operational.Registrations.Where(item => item.Status == "approved").Select(registration =>
        {
            var profile = operational.Profiles.FirstOrDefault(item => item.UserId == registration.UserId);
            if (profile is null || !InScope(user, profile)) return null;
            var output = RegistrationJson(workflow, operational, registration);
            var candidate = profile.Data.DeepClone().AsObject();
            candidate["idNumber"] = MaskId(candidate["idNumber"]?.GetValue<string>() ?? "");
            output["candidate"] = candidate;
            var schoolClass = workflow.Classes.FirstOrDefault(item => item.Id == profile.ClassId);
            output["schoolName"] = workflow.Schools.FirstOrDefault(item => item.Id == profile.SchoolId)?.Name ??
                                   profile.Data["school"]?.GetValue<string>() ?? "";
            output["gradeName"] = schoolClass?.Grade ?? "";
            output["className"] = schoolClass?.Name ?? profile.Data["grade"]?.GetValue<string>() ?? "";
            output["amountDue"] = AmountDue(output["subjects"] as JsonArray);
            output["paidByName"] = operational.Users.FirstOrDefault(item => item.Id == registration.PaidBy)?.DisplayName ?? "";
            return output;
        }).Where(item => item is not null).Cast<JsonObject>().ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["scopeLabel"] = ScopeLabel(user, workflow),
            ["canConfirmPayment"] = true,
            ["canUpdatePayment"] = true,
            ["registrations"] = new JsonArray(registrations)
        });
    }

    private static JsonObject RegistrationJson(
        AdminAccountBatchSnapshot workflow,
        AdminOperationalSnapshot operational,
        OperationalRegistration registration)
    {
        var output = registration.Data.DeepClone().AsObject();
        var exam = operational.Exams.FirstOrDefault(item => item.Id == registration.ExamId);
        var subjectIds = output["subjectIds"]?.AsArray().Select(item => item?.GetValue<string>() ?? "").ToHashSet(StringComparer.Ordinal) ?? [];
        var subjects = (exam?.Subjects ?? []).Where(item => subjectIds.Contains(item["id"]?.GetValue<string>() ?? ""))
            .Select(item => item.DeepClone()).ToArray();
        output["exam"] = exam?.Data.DeepClone();
        output["subjects"] = new JsonArray(subjects);
        output["amountDue"] = AmountDue(output["subjects"] as JsonArray);
        output["paidByName"] = operational.Users.FirstOrDefault(item => item.Id == registration.PaidBy)?.DisplayName ?? "";
        var instance = WorkflowInstance(workflow, "registration_review", registration.Id);
        output["workflow"] = instance is null ? null : WorkflowJson(workflow, instance);
        return output;
    }

    private static AccountWorkflowInstance? WorkflowInstance(AdminAccountBatchSnapshot snapshot, string businessType, string businessId) =>
        snapshot.Instances.FirstOrDefault(item => item.BusinessType == businessType && item.BusinessId == businessId && item.Status == "pending") ??
        snapshot.Instances.FirstOrDefault(item => item.BusinessType == businessType && item.BusinessId == businessId);

    private static JsonObject WorkflowJson(AdminAccountBatchSnapshot snapshot, AccountWorkflowInstance instance)
    {
        var workflow = snapshot.Workflows.FirstOrDefault(item => item.Id == instance.WorkflowId);
        var assignee = snapshot.Users.FirstOrDefault(item => item.Id == instance.AssigneeId);
        var actions = snapshot.Actions.Where(item => item.InstanceId == instance.Id).Select(item => ActionJson(snapshot, item)).ToArray();
        return new JsonObject
        {
            ["id"] = instance.Id,
            ["workflowId"] = instance.WorkflowId,
            ["businessType"] = instance.BusinessType,
            ["businessId"] = instance.BusinessId,
            ["status"] = instance.Status,
            ["currentStep"] = instance.CurrentStep,
            ["assigneeId"] = JsonValue.Create(instance.AssigneeId),
            ["createdAt"] = instance.CreatedAt,
            ["completedAt"] = JsonValue.Create(instance.CompletedAt),
            ["workflowName"] = workflow?.Name ?? "未命名流程",
            ["steps"] = new JsonArray((workflow?.Steps ?? []).Select(StepJson).ToArray()),
            ["currentStepDetail"] = workflow?.Steps.FirstOrDefault(item => item.Position == instance.CurrentStep) is { } step ? StepJson(step) : null,
            ["assignee"] = assignee is null ? null : SafeUser(assignee),
            ["actions"] = new JsonArray(actions)
        };
    }

    private async Task<ResolvedAdmin> ResolveAsync(string token, CancellationToken cancellationToken)
    {
        if (token.Length == 0) return ResolvedAdmin.Failed(Error(401, "请先登录"));
        var userId = await authenticationState.GetSessionUserIdAsync(token);
        if (userId is null) return ResolvedAdmin.Failed(Error(401, "请先登录"));
        var user = await authenticationRepository.FindUserByIdAsync(userId, cancellationToken);
        if (user is not { Active: true, ArchivedAt: null }) return ResolvedAdmin.Failed(Error(401, "请先登录"));
        return user.Role == "admin" ? new(user, null) : ResolvedAdmin.Failed(Error(403, "当前账号无权执行此操作"));
    }

    private static bool InScope(AuthenticationUser user, OperationalProfile profile) => Level(user) switch
    {
        "super" => true,
        "school" => user.SchoolId is not null && profile.SchoolId == user.SchoolId,
        _ => user.ClassId is not null && profile.ClassId == user.ClassId
    };

    private static string ScopeLabel(AuthenticationUser user, AdminAccountBatchSnapshot snapshot)
    {
        if (Level(user) == "super") return "全部学校与班级";
        var school = snapshot.Schools.FirstOrDefault(item => item.Id == user.SchoolId)?.Name ?? "未绑定学校";
        if (Level(user) == "school") return school;
        var schoolClass = snapshot.Classes.FirstOrDefault(item => item.Id == user.ClassId)?.Name ?? "未绑定班级";
        return $"{school} · {schoolClass}";
    }

    private static double AmountDue(JsonArray? subjects) =>
        Math.Round(subjects?.OfType<JsonObject>().Sum(item => item["fee"]?.GetValue<double>() ?? 0) ?? 0, 2, MidpointRounding.AwayFromZero);

    private static string MaskId(string value) => value.Length > 8 ? $"{value[..4]}********{value[^4..]}" : value;
    private static DateTimeOffset ParseDate(string? value) => DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed) ? parsed : DateTimeOffset.MinValue;
    private static string Level(AuthenticationUser user) => user.AdminLevel ?? "super";
    private static JsonObject SchoolJson(AdminSchool item) => new() { ["id"] = item.Id, ["name"] = item.Name, ["code"] = item.Code, ["address"] = item.Address, ["isSourceSchool"] = item.IsSourceSchool, ["isAdmissionSchool"] = item.IsAdmissionSchool, ["active"] = item.Active };
    private static JsonObject ClassJson(AdminClass item) => new() { ["id"] = item.Id, ["schoolId"] = item.SchoolId, ["name"] = item.Name, ["grade"] = item.Grade, ["active"] = item.Active };
    private static JsonObject StepJson(AccountWorkflowStep item) => new() { ["id"] = item.Id, ["name"] = item.Name, ["adminLevel"] = item.AdminLevel, ["position"] = item.Position };
    private static JsonObject ActionJson(AdminAccountBatchSnapshot snapshot, AccountWorkflowAction item) => new() { ["id"] = item.Id, ["instanceId"] = item.InstanceId, ["actorId"] = JsonValue.Create(item.ActorId), ["action"] = item.Action, ["note"] = item.Note, ["fromAssigneeId"] = JsonValue.Create(item.FromAssigneeId), ["toAssigneeId"] = JsonValue.Create(item.ToAssigneeId), ["createdAt"] = item.CreatedAt, ["actorName"] = snapshot.Users.FirstOrDefault(user => user.Id == item.ActorId)?.DisplayName ?? "系统", ["fromAssigneeName"] = snapshot.Users.FirstOrDefault(user => user.Id == item.FromAssigneeId)?.DisplayName ?? "", ["toAssigneeName"] = snapshot.Users.FirstOrDefault(user => user.Id == item.ToAssigneeId)?.DisplayName ?? "" };
    private static JsonObject SafeUser(AdminUser item) => new() { ["id"] = item.Id, ["username"] = item.Username, ["role"] = item.Role, ["adminLevel"] = item.Role == "admin" ? item.AdminLevel ?? "super" : null, ["schoolId"] = JsonValue.Create(item.SchoolId), ["classId"] = JsonValue.Create(item.ClassId), ["displayName"] = item.DisplayName, ["candidateNumber"] = JsonValue.Create(item.CandidateNumber), ["mustChangePassword"] = item.MustChangePassword, ["totpEnabled"] = item.TotpEnabled, ["archived"] = item.ArchivedAt is not null };
    private static AdminEndpointResult Success(JsonObject body) => new(200, body);
    private static AdminEndpointResult Error(int status, string message) => new(status, new JsonObject { ["ok"] = false, ["message"] = message });
    private sealed record ResolvedAdmin(AuthenticationUser? User, AdminEndpointResult? Error) { public static ResolvedAdmin Failed(AdminEndpointResult error) => new(null, error); }
}
