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

namespace Eis.Infrastructure.Administration;

internal sealed class AdminWorkflowService(
    IAuthenticationStateStore authenticationState,
    AuthenticationRepository authenticationRepository,
    AdminAccountBatchSnapshotLoader workflowSnapshotLoader,
    AdminOperationalSnapshotLoader operationalSnapshotLoader,
    AdminCenterSnapshotLoader centerSnapshotLoader,
    AdminWorkflowResultSnapshotLoader resultSnapshotLoader,
    AdminWorkflowRepository repository) : IAdminWorkflowService
{
    public async Task<AdminEndpointResult> GetAsync(string sessionToken, CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var snapshots = await LoadAsync(cancellationToken);
        var instances = snapshots.Workflow.Instances
            .Where(instance => Level(user) == "super" || InScope(user, Scope(snapshots, instance)))
            .Select(instance => InstanceView(snapshots, instance))
            .ToArray();
        var availableAdmins = snapshots.Workflow.Users
            .Where(item => item.Role == "admin" && item.Active)
            .Select(SafeUser)
            .ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["instances"] = new JsonArray(instances),
            ["availableAdmins"] = new JsonArray(availableAdmins),
            ["canSupervise"] = Level(user) == "super"
        });
    }

    public async Task<AdminEndpointResult> TransferAsync(
        string sessionToken,
        string instanceId,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var snapshots = await LoadAsync(cancellationToken);
        var instance = snapshots.Workflow.Instances.FirstOrDefault(item => item.Id == instanceId && item.Status == "pending");
        if (instance is null) return Error(404, "待处理流程不存在");
        var workflow = snapshots.Workflow.Workflows.FirstOrDefault(item => item.Id == instance.WorkflowId);
        var step = workflow?.Steps.FirstOrDefault(item => item.Position == instance.CurrentStep);
        if (Level(user) != "super" && instance.AssigneeId != user.Id)
            return Error(403, "只有当前处理人可以转交该流程");
        var assigneeId = Text(body["assigneeId"]);
        var target = snapshots.Workflow.Users.FirstOrDefault(item =>
            item.Id == assigneeId && item.Role == "admin" && item.Active && item.AdminLevel == step?.AdminLevel);
        if (target is null) return Error(400, "只能转交给当前步骤同级管理员");
        var scope = Scope(snapshots, instance);
        if (step?.AdminLevel == "school" && target.SchoolId != scope?.SchoolId)
            return Error(400, "校级流程只能转交给本校同级管理员");
        if (step?.AdminLevel == "class" && (target.SchoolId != scope?.SchoolId || target.ClassId != scope?.ClassId))
            return Error(400, "班级流程只能转交给本班同级管理员");

        var updated = instance with { AssigneeId = target.Id };
        var action = new AccountWorkflowAction(
            Uid("flow_action"), instance.Id, user.Id, "transfer", Clean(Text(body["note"]), 300),
            instance.AssigneeId, target.Id, NowIso());
        await repository.TransferAsync(
            updated,
            action,
            Audit(user, "转交审批流程", $"{workflow?.Name ?? "未命名流程"} → {target.DisplayName}"),
            cancellationToken);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["workflow"] = WorkflowJson(snapshots.Workflow, updated, action)
        });
    }

    public async Task<AdminEndpointResult> SuperviseAsync(
        string sessionToken,
        string instanceId,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        if (Level(user) != "super") return Error(403, "当前管理员层级无权执行此操作");
        var snapshots = await LoadAsync(cancellationToken);
        var instance = snapshots.Workflow.Instances.FirstOrDefault(item => item.Id == instanceId);
        if (instance is null) return Error(404, "流程不存在");
        if (instance.BusinessType == "candidate_account_batch" && snapshots.Workflow.Items.Any(item => item.BatchId == instance.BusinessId && item.UserId is not null))
            return Error(409, "已生成账号的批次不可重新打开，避免重复建号");
        var workflow = snapshots.Workflow.Workflows.FirstOrDefault(item => item.Id == instance.WorkflowId);
        if (workflow is null || workflow.Steps.Count == 0) return Error(409, "流程定义不存在或没有审批步骤");
        var requestedNumber = JsBoolean(body["currentStep"])
            ? Number(body["currentStep"])
            : instance.CurrentStep;
        if (!double.IsFinite(requestedNumber)) return Error(400, "目标步骤无效");
        var requestedStep = (int)Math.Min(workflow.Steps.Count, Math.Max(1, requestedNumber));
        var step = workflow.Steps.FirstOrDefault(item => item.Position == requestedStep);
        if (step is null) return Error(400, "目标步骤无效");
        var scope = Scope(snapshots, instance);
        var eligible = EligibleAdmins(snapshots.Workflow, step.AdminLevel, scope).ToArray();
        var requestedAssigneeId = JsBoolean(body["assigneeId"]) ? Text(body["assigneeId"]) : "";
        var requestedAssignee = requestedAssigneeId.Length == 0
            ? null
            : eligible.FirstOrDefault(item => item.Id == requestedAssigneeId);
        if (requestedAssigneeId.Length > 0 && requestedAssignee is null)
            return Error(400, "指定管理员不在该学校或班级的目标步骤范围内");
        var assignee = requestedAssignee ?? SelectAssignee(snapshots.Workflow, eligible);
        if (assignee is null) return Error(409, "目标步骤没有可用管理员");

        var note = Clean(Text(body["note"]), 300);
        if (note.Length == 0) note = $"超级管理员将流程调整到第 {requestedStep} 步";
        var updated = instance with
        {
            Status = "pending",
            CompletedAt = null,
            CurrentStep = requestedStep,
            AssigneeId = assignee.Id
        };
        var action = new AccountWorkflowAction(
            Uid("flow_action"), instance.Id, user.Id,
            requestedStep < instance.CurrentStep ? "return" : "supervise",
            note, instance.AssigneeId, assignee.Id, NowIso());
        var business = instance.BusinessType is "profile_change" or "registration_review" or "center_change" or "candidate_account_batch"
            ? new WorkflowBusinessReset(instance.BusinessType, instance.BusinessId, note)
            : null;
        await repository.SuperviseAsync(
            updated,
            action,
            business,
            Audit(user, "监督调整审批流程", $"{workflow.Name} · 第 {requestedStep} 步 · {assignee.DisplayName}"),
            cancellationToken);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["workflow"] = WorkflowJson(snapshots.Workflow, updated, action)
        });
    }

    private async Task<WorkflowSnapshots> LoadAsync(CancellationToken cancellationToken)
    {
        var workflow = await workflowSnapshotLoader.LoadAsync(cancellationToken);
        var operational = await operationalSnapshotLoader.LoadAsync(cancellationToken);
        var centers = await centerSnapshotLoader.LoadAsync(cancellationToken);
        var results = await resultSnapshotLoader.LoadAsync(cancellationToken);
        return new(workflow, operational, centers, results);
    }

    private static JsonObject InstanceView(WorkflowSnapshots snapshots, AccountWorkflowInstance instance)
    {
        var scope = Scope(snapshots, instance);
        var output = WorkflowJson(snapshots.Workflow, instance);
        output["candidateName"] = scope?.Name ?? "";
        output["schoolName"] = scope?.SchoolName ?? "";
        output["className"] = scope?.ClassName ?? "";
        var registration = instance.BusinessType == "registration_review"
            ? snapshots.Operational.Registrations.FirstOrDefault(item => item.Id == instance.BusinessId)
            : null;
        output["examName"] = registration is null
            ? ""
            : snapshots.Operational.Exams.FirstOrDefault(item => item.Id == registration.ExamId)?.Data["name"]?.GetValue<string>() ?? "";
        var centerChange = instance.BusinessType == "center_change"
            ? snapshots.Centers.Changes.FirstOrDefault(item => item.Id == instance.BusinessId)
            : null;
        output["centerName"] = centerChange?.Name ?? "";
        output["requestType"] = centerChange?.RequestType ?? "";
        output["centerChange"] = centerChange is null
            ? null
            : AdminCenterService.ChangeJson(snapshots.Workflow, snapshots.Centers, centerChange);
        var batch = instance.BusinessType == "candidate_account_batch"
            ? snapshots.Workflow.Batches.FirstOrDefault(item => item.Id == instance.BusinessId)
            : null;
        output["accountBatch"] = batch is null ? null : AdminAccountBatchService.BatchView(snapshots.Workflow, batch);
        output["batchTotalCount"] = batch is null ? 0 : snapshots.Workflow.Items.Count(item => item.BatchId == batch.Id);
        var appealResult = instance.BusinessType == "score_appeal"
            ? snapshots.Results.FirstOrDefault(item => item.Id == instance.BusinessId)
            : null;
        output["appealResult"] = appealResult is null ? null : AppealResultJson(snapshots, appealResult);
        return output;
    }

    private static WorkflowScope? Scope(WorkflowSnapshots snapshots, AccountWorkflowInstance instance)
    {
        OperationalProfile? profile = null;
        if (instance.BusinessType == "profile_change")
            profile = snapshots.Operational.Profiles.FirstOrDefault(item => item.Id == instance.BusinessId);
        else if (instance.BusinessType == "registration_review")
        {
            var registration = snapshots.Operational.Registrations.FirstOrDefault(item => item.Id == instance.BusinessId);
            profile = snapshots.Operational.Profiles.FirstOrDefault(item => item.UserId == registration?.UserId);
        }
        else if (instance.BusinessType == "score_appeal")
        {
            var result = snapshots.Results.FirstOrDefault(item => item.Id == instance.BusinessId);
            var registration = snapshots.Operational.Registrations.FirstOrDefault(item => item.Id == result?.RegistrationId);
            profile = snapshots.Operational.Profiles.FirstOrDefault(item => item.UserId == registration?.UserId);
        }
        if (profile is not null)
        {
            return new(
                profile.SchoolId,
                profile.ClassId,
                profile.Data["name"]?.GetValue<string>() ?? "",
                profile.Data["school"]?.GetValue<string>() ?? "",
                profile.Data["grade"]?.GetValue<string>() ?? "");
        }
        var schoolId = instance.BusinessType switch
        {
            "center_change" => snapshots.Centers.Changes.FirstOrDefault(item => item.Id == instance.BusinessId)?.SchoolId,
            "candidate_account_batch" => snapshots.Workflow.Batches.FirstOrDefault(item => item.Id == instance.BusinessId)?.SchoolId,
            _ => null
        };
        if (schoolId is null) return null;
        return new(
            schoolId,
            null,
            "",
            snapshots.Workflow.Schools.FirstOrDefault(item => item.Id == schoolId)?.Name ?? "",
            "");
    }

    private static JsonObject AppealResultJson(WorkflowSnapshots snapshots, AdminWorkflowResult result)
    {
        var registration = snapshots.Operational.Registrations.FirstOrDefault(item => item.Id == result.RegistrationId);
        var exam = snapshots.Operational.Exams.FirstOrDefault(item => item.Id == registration?.ExamId);
        var subject = exam?.Subjects.FirstOrDefault(item => item["id"]?.GetValue<string>() == result.SubjectId);
        return new JsonObject
        {
            ["score"] = result.Score,
            ["grade"] = result.Grade,
            ["rank"] = JsonValue.Create(result.Rank),
            ["cohortSize"] = result.CohortSize,
            ["rankPercent"] = JsonValue.Create(result.RankPercent),
            ["examName"] = exam?.Data["name"]?.GetValue<string>() ?? "",
            ["examCode"] = exam?.Data["code"]?.GetValue<string>() ?? "",
            ["subjectName"] = subject?["name"]?.GetValue<string>() ?? "",
            ["fullScore"] = JsonValue.Create(NullableNumber(subject?["fullScore"])),
            ["passRule"] = subject?["passRule"]?.GetValue<string>() ?? "fixed_score",
            ["passValue"] = JsonValue.Create(NullableNumber(subject?["passValue"] ?? subject?["passScore"])),
            ["passScore"] = JsonValue.Create(result.PassScore),
            ["cutoffRank"] = JsonValue.Create(result.CutoffRank),
            ["passText"] = PassText(subject),
            ["qualified"] = JsonValue.Create(result.Qualified)
        };
    }

    private static string PassText(JsonObject? subject)
    {
        var rule = subject?["passRule"]?.GetValue<string>() ?? "fixed_score";
        if (rule == "none") return "不设单科线";
        if (rule == "rank_percent") return $"本科排名前 {Format(NullableNumber(subject?["passValue"]) ?? 60)}% 达线";
        return $"固定 {Format(Math.Round(NullableNumber(subject?["passValue"] ?? subject?["passScore"]) ?? 0, 2, MidpointRounding.AwayFromZero))} 分";
    }

    private static IEnumerable<AdminUser> EligibleAdmins(
        AdminAccountBatchSnapshot snapshot,
        string level,
        WorkflowScope? scope) => snapshot.Users.Where(item =>
        item.Role == "admin" && item.Active && item.AdminLevel == level && level switch
        {
            "super" => true,
            "school" => scope?.SchoolId is not null && item.SchoolId == scope.SchoolId,
            "class" => scope?.SchoolId is not null && scope.ClassId is not null &&
                       item.SchoolId == scope.SchoolId && item.ClassId == scope.ClassId,
            _ => false
        });

    private static AdminUser? SelectAssignee(AdminAccountBatchSnapshot snapshot, IReadOnlyList<AdminUser> eligible)
    {
        var pending = snapshot.Instances.Where(item => item.Status == "pending" && item.AssigneeId is not null)
            .GroupBy(item => item.AssigneeId!).ToDictionary(group => group.Key, group => group.Count(), StringComparer.Ordinal);
        var assigned = snapshot.Actions.Where(item => item.ToAssigneeId is not null)
            .GroupBy(item => item.ToAssigneeId!).ToDictionary(group => group.Key, group => group.Count(), StringComparer.Ordinal);
        return eligible.OrderBy(item => pending.GetValueOrDefault(item.Id))
            .ThenBy(item => assigned.GetValueOrDefault(item.Id))
            .ThenBy(item => item.CreatedAt, StringComparer.Ordinal)
            .ThenBy(item => item.Id, StringComparer.Ordinal)
            .FirstOrDefault();
    }

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

    private static JsonObject WorkflowJson(
        AdminAccountBatchSnapshot snapshot,
        AccountWorkflowInstance instance,
        AccountWorkflowAction? extraAction = null)
    {
        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)
            .Concat(extraAction is null ? [] : [extraAction])
            .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 double Number(JsonNode? node) => double.TryParse(Text(node), NumberStyles.Float, CultureInfo.InvariantCulture, out var value) ? value : double.NaN;
    private static double? NullableNumber(JsonNode? node) => node is not null && double.TryParse(Text(node), NumberStyles.Float, CultureInfo.InvariantCulture, out var value) ? value : null;
    private static string Text(JsonNode? node) => node is JsonValue value && value.TryGetValue<string>(out var text) ? text : node?.ToString() ?? "";
    private static string Clean(string value, int maximum) { var cleaned = value.Trim(); return cleaned[..Math.Min(cleaned.Length, maximum)]; }
    private static bool JsBoolean(JsonNode? node) { if (node is null) return false; if (node is JsonValue value && value.TryGetValue<bool>(out var boolean)) return boolean; if (node is JsonValue textValue && textValue.TryGetValue<string>(out var text)) return text.Length > 0; return true; }
    private static string Format(double value) => value.ToString("0.##", CultureInfo.InvariantCulture);
    private static string Level(AuthenticationUser user) => user.AdminLevel ?? "super";
    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(buffer[position..]); }
    private static AdminAuditEntry Audit(AuthenticationUser user, string action, string detail) => new(Uid("log"), user.Id, action, detail, NowIso());
    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 WorkflowSnapshots(AdminAccountBatchSnapshot Workflow, AdminOperationalSnapshot Operational, AdminCenterSnapshot Centers, IReadOnlyList<AdminWorkflowResult> Results);
    private sealed record WorkflowScope(string? SchoolId, string? ClassId, string Name, string SchoolName, string ClassName);
    private sealed record ResolvedAdmin(AuthenticationUser? User, AdminEndpointResult? Error) { public static ResolvedAdmin Failed(AdminEndpointResult error) => new(null, error); }
}
