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 AdminCandidateManagementService(
    IAuthenticationStateStore authenticationState,
    AuthenticationRepository authenticationRepository,
    PasswordCompatibilityService passwords,
    AdminAccountBatchSnapshotLoader workflowSnapshotLoader,
    AdminOperationalSnapshotLoader operationalSnapshotLoader,
    AdminWriteRepository repository) : IAdminCandidateManagementService
{
    public async Task<AdminEndpointResult> ArchiveAccountsAsync(
        string sessionToken,
        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) != "school") return Error(403, "考生账户归档由校级管理员负责");

        var scopeType = Clean(Text(body["scopeType"]), 20);
        var scopeValue = Clean(Text(body["scopeValue"]), 100);
        var archived = JsBoolean(body["archived"]);
        if (scopeType is not ("class" or "grade") || scopeValue.Length == 0)
            return Error(400, "请选择要归档的班级或年级");

        var workflow = await workflowSnapshotLoader.LoadAsync(cancellationToken);
        var operational = await operationalSnapshotLoader.LoadAsync(cancellationToken);
        var scopedClasses = workflow.Classes.Where(item => item.SchoolId == user.SchoolId).ToArray();
        var targetClassIds = scopeType == "class"
            ? scopedClasses.Where(item => item.Id == scopeValue).Select(item => item.Id).ToHashSet(StringComparer.Ordinal)
            : scopedClasses.Where(item => item.Grade == scopeValue).Select(item => item.Id).ToHashSet(StringComparer.Ordinal);
        if (targetClassIds.Count == 0) return Error(404, "所选班级或年级不在本校范围内");

        var targetUserIds = operational.Profiles
            .Where(profile => profile.SchoolId == user.SchoolId && profile.ClassId is not null && targetClassIds.Contains(profile.ClassId))
            .Select(profile => profile.UserId)
            .ToHashSet(StringComparer.Ordinal);
        var targets = workflow.Users
            .Where(item => item.Role == "candidate" && targetUserIds.Contains(item.Id) && (item.ArchivedAt is not null) != archived)
            .ToArray();
        var changedAt = archived ? NowIso() : null;
        var scopeLabel = scopeType == "class"
            ? workflow.Classes.FirstOrDefault(item => item.Id == scopeValue)?.Name ?? ""
            : scopeValue;
        await repository.UpdateCandidateArchivesAsync(
            targets.Select(item => item.Id).ToArray(),
            changedAt,
            archived ? user.Id : null,
            Audit(user, archived ? "批量归档考生账户" : "批量恢复考生账户", $"{scopeLabel} · {targets.Length} 个账户"),
            cancellationToken);
        if (archived && targets.Length > 0)
        {
            await authenticationState.DeleteUsersSessionsAsync(targets.Select(item => item.Id));
        }

        return Success(new JsonObject
        {
            ["ok"] = true,
            ["archived"] = archived,
            ["count"] = targets.Length,
            ["scopeLabel"] = scopeLabel
        });
    }

    public async Task<AdminEndpointResult> ResetPasswordAsync(
        string sessionToken,
        string profileId,
        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 workflow = await workflowSnapshotLoader.LoadAsync(cancellationToken);
        var operational = await operationalSnapshotLoader.LoadAsync(cancellationToken);
        var profile = operational.Profiles.FirstOrDefault(item => item.Id == profileId);
        var target = workflow.Users.FirstOrDefault(item => item.Id == profile?.UserId && item.Role == "candidate");
        if (profile is null || target is null) return Error(404, "考生账户不存在");
        if (target.ArchivedAt is not null) return Error(409, "归档账户需由校方恢复后才能重置密码");

        var temporaryPassword = $"Reset-{Base64Url(RandomNumberGenerator.GetBytes(7))}";
        var name = profile.Data["name"]?.GetValue<string>() ?? "";
        await repository.ResetCandidatePasswordAsync(
            target.Id,
            passwords.Hash(temporaryPassword),
            Audit(user, "重置考生密码", $"{target.CandidateNumber} · {name}"),
            cancellationToken);
        await authenticationState.DeleteUserSessionsAsync(target.Id);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["candidateNumber"] = JsonValue.Create(target.CandidateNumber),
            ["temporaryPassword"] = temporaryPassword
        });
    }

    public async Task<AdminEndpointResult> ReviewProfileAsync(
        string sessionToken,
        string profileId,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var status = Text(body["status"]);
        if (status is not ("approved" or "rejected")) return Error(400, "审核状态无效");

        var workflowSnapshot = await workflowSnapshotLoader.LoadAsync(cancellationToken);
        var operational = await operationalSnapshotLoader.LoadAsync(cancellationToken);
        var profile = operational.Profiles.FirstOrDefault(item => item.Id == profileId);
        if (profile is null) return Error(404, "考生资料不存在");
        if (Level(user) != "super" && !InScope(user, profile)) return Error(403, "该考生不在你的数据范围内");

        var instance = workflowSnapshot.Instances.FirstOrDefault(item =>
            item.BusinessType == "profile_change" && item.BusinessId == profile.Id && item.Status == "pending");
        if (instance is null) return Error(409, "当前没有待处理的考生信息流程");
        var workflow = workflowSnapshot.Workflows.FirstOrDefault(item => item.Id == instance.WorkflowId);
        var step = workflow?.Steps.FirstOrDefault(item => item.Position == instance.CurrentStep);
        if (workflow is null || step is null) return Error(409, "考生信息审批流程状态异常");
        if (Level(user) != "super" && (instance.AssigneeId != user.Id || step.AdminLevel != Level(user)))
            return Error(403, "该流程当前未分配给你，可由当前处理人转交");

        var note = Clean(Text(body["reviewNote"]), 300);
        var action = new AccountWorkflowAction(
            Uid("flow_action"), instance.Id, user.Id, status == "approved" ? "approve" : "reject",
            note, instance.AssigneeId, null, NowIso());
        var updatedInstance = instance;
        var output = profile.Data.DeepClone().AsObject();
        if (status == "rejected")
        {
            updatedInstance = instance with { Status = "rejected", CompletedAt = NowIso(), AssigneeId = null };
            output["status"] = "rejected";
            output["reviewNote"] = note;
            output["reviewedAt"] = NowIso();
            output["reviewerId"] = user.Id;
        }
        else if (instance.CurrentStep < workflow.Steps.Count)
        {
            var nextStep = workflow.Steps.FirstOrDefault(item => item.Position == instance.CurrentStep + 1);
            var nextAssignee = nextStep is null ? null : SelectAssignee(workflowSnapshot, nextStep.AdminLevel, profile);
            if (nextAssignee is null) return Error(409, $"没有可承接“{nextStep?.Name ?? "下一审批步骤"}”的管理员");
            updatedInstance = instance with { CurrentStep = instance.CurrentStep + 1, AssigneeId = nextAssignee.Id };
            action = action with { ToAssigneeId = nextAssignee.Id };
            output["status"] = "pending";
            output["reviewNote"] = note;
        }
        else
        {
            updatedInstance = instance with { Status = "approved", CompletedAt = NowIso(), AssigneeId = null };
            output["status"] = "approved";
            output["reviewNote"] = note;
            output["reviewedAt"] = NowIso();
            output["reviewerId"] = user.Id;
        }

        var profileStatus = output["status"]?.GetValue<string>() ?? "pending";
        var reviewedAt = output["reviewedAt"]?.GetValue<string>();
        var reviewerId = output["reviewerId"]?.GetValue<string>();
        var name = output["name"]?.GetValue<string>() ?? "";
        await repository.ProcessProfileWorkflowAsync(
            updatedInstance,
            action,
            profile.Id,
            profileStatus,
            note,
            reviewedAt,
            reviewerId,
            Audit(user, status == "approved" ? "处理考生信息流程" : "退回考生信息", $"{name}：{(note.Length == 0 ? "无备注" : note)}"),
            cancellationToken);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["profile"] = output,
            ["workflow"] = WorkflowJson(workflowSnapshot, updatedInstance, action)
        });
    }

    private static AdminUser? SelectAssignee(
        AdminAccountBatchSnapshot snapshot,
        string level,
        OperationalProfile profile)
    {
        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 snapshot.Users
            .Where(item => item.Role == "admin" && item.Active && item.AdminLevel == level && level switch
            {
                "super" => true,
                "school" => profile.SchoolId is not null && item.SchoolId == profile.SchoolId,
                "class" => profile.SchoolId is not null && profile.ClassId is not null &&
                           item.SchoolId == profile.SchoolId && item.ClassId == profile.ClassId,
                _ => false
            })
            .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 JsonObject WorkflowJson(
        AdminAccountBatchSnapshot snapshot,
        AccountWorkflowInstance instance,
        AccountWorkflowAction action)
    {
        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).Append(action)
            .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 { } currentStep ? StepJson(currentStep) : 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 bool JsBoolean(JsonNode? node)
    {
        if (node is null) return false;
        if (node is not JsonValue value) return true;
        if (value.TryGetValue<bool>(out var boolean)) return boolean;
        if (value.TryGetValue<string>(out var text)) return text.Length > 0;
        if (value.TryGetValue<double>(out var number)) return number != 0 && !double.IsNaN(number);
        return true;
    }

    private static string Text(JsonNode? node)
    {
        if (node is null) return "";
        if (node is JsonValue value && value.TryGetValue<string>(out var text)) return text;
        return node.ToString();
    }

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

    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 Base64Url(byte[] value) => Convert.ToBase64String(value).TrimEnd('=').Replace('+', '-').Replace('/', '_');
    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 ResolvedAdmin(AuthenticationUser? User, AdminEndpointResult? Error)
    {
        public static ResolvedAdmin Failed(AdminEndpointResult error) => new(null, error);
    }
}
