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 AdminConfigurationService(
    IAuthenticationStateStore authenticationState,
    AuthenticationRepository authenticationRepository,
    AdminAccountBatchSnapshotLoader snapshotLoader,
    AdminConfigurationRepository repository) : IAdminConfigurationService
{
    private static readonly HashSet<string> SegmentTypes = ["year", "school_code", "gender", "sequence", "literal"];
    private static readonly HashSet<string> AdminLevels = ["class", "school", "super"];
    private static readonly HashSet<string> WorkflowTypes = ["profile_change", "registration_review", "center_change", "candidate_account_batch", "score_appeal"];

    public async Task<AdminEndpointResult> GetNumberRulesAsync(string sessionToken, CancellationToken cancellationToken)
    {
        var context = await ResolveSuperAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var active = snapshot.NumberRules.FirstOrDefault(item => item.Active);
        var preview = active is null ? "" : GenerateCandidateNumber(snapshot, active);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["rules"] = new JsonArray(snapshot.NumberRules.Select(RuleJson).ToArray()),
            ["activeRule"] = active is null ? null : RuleJson(active),
            ["preview"] = preview
        });
    }

    public async Task<AdminEndpointResult> SaveNumberRuleAsync(string sessionToken, JsonObject body, CancellationToken cancellationToken)
    {
        var context = await ResolveSuperAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var requestedArray = body["segments"] as JsonArray;
        var requested = requestedArray?.Select(item => item as JsonObject).ToArray() ?? [];
        if (requested.Length == 0 || requested.Any(item => item is null || !SegmentTypes.Contains(Text(item["type"]))) ||
            !requested.Any(item => item is not null && Text(item["type"]) == "sequence"))
            return Error(400, "报名号规则至少包含一个流水号段");

        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var existing = snapshot.NumberRules.FirstOrDefault(item => item.Id == Text(body["id"]));
        var name = Clean(Text(body["name"]), 80);
        var rule = new AccountNumberRule(
            existing?.Id ?? Uid("rule"),
            name.Length == 0 ? "自定义报名号规则" : name,
            Clean(Text(body["separator"]), 3),
            true,
            user.Id,
            NowIso(),
            requested.Select((item, index) => new AccountNumberSegment(
                Uid("segment"),
                Text(item!["type"]),
                Clean(Text(item["value"]), 20),
                Math.Clamp((int)NumberOrZero(item["width"]), 0, 12),
                index + 1)).ToArray());
        var audit = Audit(user, "更新报名号规则", $"{rule.Name} · {string.Join(" + ", rule.Segments.Select(item => item.Type))}");
        await repository.SaveNumberRuleAsync(rule, existing is null, audit, cancellationToken);
        return Success(new JsonObject { ["ok"] = true, ["rule"] = RuleJson(rule) });
    }

    public async Task<AdminEndpointResult> GetWorkflowsAsync(string sessionToken, CancellationToken cancellationToken)
    {
        var context = await ResolveSuperAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["workflows"] = new JsonArray(snapshot.Workflows.Select(WorkflowJson).ToArray())
        });
    }

    public async Task<AdminEndpointResult> SaveWorkflowAsync(
        string sessionToken,
        string businessType,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveSuperAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        if (!WorkflowTypes.Contains(businessType)) return Error(404, "审批流程不存在");
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var existing = snapshot.Workflows.FirstOrDefault(item => item.BusinessType == businessType && item.Active);
        if (existing is null) return Error(404, "审批流程不存在");
        var stepArray = body["steps"] as JsonArray;
        var requested = stepArray?.Select(item => item as JsonObject).ToArray() ?? [];
        if (requested.Length == 0 || requested.Any(item => item is null || !AdminLevels.Contains(Text(item["adminLevel"]))))
            return Error(400, "流程至少需要一个班级、校级或超级管理员审批步骤");
        if (businessType is "center_change" or "candidate_account_batch" && requested.Any(item => Text(item!["adminLevel"]) == "class"))
            return Error(400, "该业务不对应单一班级，不能配置班级管理员审批步骤");
        if (businessType == "candidate_account_batch" && Text(requested[^1]!["adminLevel"]) != "super")
            return Error(400, "批量报名号申领的最终步骤必须由超级管理员审批");
        var requestedName = Clean(Text(body["name"]), 80);
        var workflow = existing with
        {
            Name = requestedName.Length == 0 ? existing.Name : requestedName,
            UpdatedBy = user.Id,
            UpdatedAt = NowIso(),
            Steps = requested.Select((item, index) =>
            {
                var requestedStepName = Clean(Text(item!["name"]), 80);
                return new AccountWorkflowStep(
                    Uid("workflow_step"),
                    requestedStepName.Length == 0 ? $"第 {index + 1} 步" : requestedStepName,
                    Text(item["adminLevel"]),
                    index + 1);
            }).ToArray()
        };
        await repository.SaveWorkflowAsync(
            workflow,
            Audit(user, "修改审批流程", $"{workflow.Name} · {workflow.Steps.Count} 个步骤"),
            cancellationToken);
        return Success(new JsonObject { ["ok"] = true, ["workflow"] = WorkflowJson(workflow) });
    }

    private async Task<ResolvedAdmin> ResolveSuperAsync(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, "请先登录"));
        if (user.Role != "admin") return ResolvedAdmin.Failed(Error(403, "当前账号无权执行此操作"));
        return (user.AdminLevel ?? "super") == "super"
            ? new ResolvedAdmin(user, null)
            : ResolvedAdmin.Failed(Error(403, "当前管理员层级无权执行此操作"));
    }

    private static string GenerateCandidateNumber(AdminAccountBatchSnapshot snapshot, AccountNumberRule rule)
    {
        var year = DateTime.Now.Year.ToString(CultureInfo.InvariantCulture);
        var school = snapshot.Schools.FirstOrDefault(item => item.Id == snapshot.PreviewSchoolId);
        var prefix = string.Join(rule.Separator, rule.Segments
            .Where(item => item.Type != "sequence")
            .Select(item => item.Type == "year" ? year : item.Type == "school_code" ? school?.Code ?? "" : "")
            .Where(item => item.Length > 0));
        var sequence = snapshot.Users.Count(item => item.Role == "candidate" && item.CandidateNumber is not null &&
            (prefix.Length == 0 || item.CandidateNumber.StartsWith(prefix, StringComparison.Ordinal))) + 1;
        return string.Join(rule.Separator, rule.Segments.Select(segment => segment.Type switch
        {
            "year" => year[^Math.Min(year.Length, Math.Max(2, segment.Width == 0 ? 4 : segment.Width))..],
            "school_code" => school?.Code ?? "NOSCHOOL",
            "gender" => snapshot.PreviewGender == "男" ? "M" : snapshot.PreviewGender == "女" ? "F" : "X",
            "sequence" => sequence.ToString(CultureInfo.InvariantCulture).PadLeft(Math.Max(1, segment.Width == 0 ? 4 : segment.Width), '0'),
            _ => Clean(segment.Value, 20).ToUpperInvariant()
        }));
    }

    private static JsonObject RuleJson(AccountNumberRule item) => new()
    {
        ["id"] = item.Id,
        ["name"] = item.Name,
        ["separator"] = item.Separator,
        ["active"] = item.Active,
        ["createdBy"] = JsonValue.Create(item.CreatedBy),
        ["updatedAt"] = item.UpdatedAt,
        ["segments"] = new JsonArray(item.Segments.Select(SegmentJson).ToArray())
    };

    private static JsonObject SegmentJson(AccountNumberSegment item) => new()
    {
        ["id"] = item.Id,
        ["type"] = item.Type,
        ["value"] = item.Value,
        ["width"] = item.Width,
        ["position"] = item.Position
    };

    private static JsonObject WorkflowJson(AccountWorkflow item) => new()
    {
        ["id"] = item.Id,
        ["businessType"] = item.BusinessType,
        ["name"] = item.Name,
        ["active"] = item.Active,
        ["updatedBy"] = JsonValue.Create(item.UpdatedBy),
        ["updatedAt"] = item.UpdatedAt,
        ["steps"] = new JsonArray(item.Steps.Select(step => new JsonObject
        {
            ["id"] = step.Id,
            ["name"] = step.Name,
            ["adminLevel"] = step.AdminLevel,
            ["position"] = step.Position
        }).ToArray())
    };

    private static double NumberOrZero(JsonNode? node)
    {
        var text = Text(node);
        return text.Length == 0 || !double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var value) ? 0 : value;
    }

    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 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 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); }
}
