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

namespace Eis.Infrastructure.Administration;

internal sealed partial class AdminOrganizationService(
    IAuthenticationStateStore authenticationState,
    AuthenticationRepository authenticationRepository,
    PasswordCompatibilityService passwords,
    AdminReadSnapshotLoader snapshotLoader,
    AdminWriteRepository repository) : IAdminOrganizationService
{
    private static readonly IReadOnlyDictionary<string, string> LevelNames = new Dictionary<string, string>(StringComparer.Ordinal)
    {
        ["super"] = "超级管理员",
        ["school"] = "校级管理员",
        ["class"] = "班级管理员"
    };

    public async Task<AdminEndpointResult> CreateSchoolAsync(
        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) != "super") return Error(403, "只有超级管理员可以创建学校");

        var name = Clean(Text(body["name"]), 100);
        var code = Clean(Text(body["code"]), 40).ToUpperInvariant();
        var address = Clean(Text(body["address"]), 200);
        var isSourceSchool = !ExactlyFalse(body["isSourceSchool"]);
        var isAdmissionSchool = !ExactlyFalse(body["isAdmissionSchool"]);
        if (name.Length == 0 || code.Length == 0) return Error(400, "学校名称和学校代码不能为空");
        if (!isSourceSchool && !isAdmissionSchool) return Error(400, "学校至少应设置为生源校或招生校");
        if (!SchoolCodePattern().IsMatch(code)) return Error(400, "学校代码只能包含字母、数字、下划线和连字符");

        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        if (snapshot.Schools.Any(item => string.Equals(item.Code, code, StringComparison.OrdinalIgnoreCase)))
            return Error(409, "学校代码已存在");
        if (snapshot.Schools.Any(item => string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase)))
            return Error(409, "学校名称已存在");

        var school = new AdminSchool(
            Uid("school"), name, code, address, isSourceSchool, isAdmissionSchool, !ExactlyFalse(body["active"]));
        await repository.SaveSchoolAsync(
            school,
            isNew: true,
            Audit(user, "创建学校", $"{name} · {code}"),
            cancellationToken);
        return Result(201, new JsonObject { ["ok"] = true, ["school"] = SchoolJson(school) });
    }

    public async Task<AdminEndpointResult> UpdateSchoolAsync(
        string sessionToken,
        string schoolId,
        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 snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var existing = snapshot.Schools.FirstOrDefault(item => item.Id == schoolId);
        if (existing is null) return Error(404, "学校不存在");
        var name = Clean(NullishText(body, "name", existing.Name), 100);
        var code = Clean(NullishText(body, "code", existing.Code), 40).ToUpperInvariant();
        var address = Clean(NullishText(body, "address", existing.Address), 200);
        var isSourceSchool = NullishBoolean(body, "isSourceSchool", existing.IsSourceSchool);
        var isAdmissionSchool = NullishBoolean(body, "isAdmissionSchool", existing.IsAdmissionSchool);
        var active = NullishBoolean(body, "active", existing.Active);
        if (name.Length == 0 || code.Length == 0) return Error(400, "学校名称和学校代码不能为空");
        if (!isSourceSchool && !isAdmissionSchool) return Error(400, "学校至少应设置为生源校或招生校");
        if (!SchoolCodePattern().IsMatch(code)) return Error(400, "学校代码只能包含字母、数字、下划线和连字符");
        if (snapshot.Schools.Any(item => item.Id != existing.Id && string.Equals(item.Code, code, StringComparison.OrdinalIgnoreCase)))
            return Error(409, "学校代码已存在");
        if (snapshot.Schools.Any(item => item.Id != existing.Id && string.Equals(item.Name, name, StringComparison.OrdinalIgnoreCase)))
            return Error(409, "学校名称已存在");

        var school = existing with
        {
            Name = name,
            Code = code,
            Address = address,
            IsSourceSchool = isSourceSchool,
            IsAdmissionSchool = isAdmissionSchool,
            Active = active
        };
        await repository.SaveSchoolAsync(
            school,
            isNew: false,
            Audit(user, "维护学校", $"{name} · {code} · {(active ? "启用" : "停用")}"),
            cancellationToken);
        return Success(new JsonObject { ["ok"] = true, ["school"] = SchoolJson(school) });
    }

    public async Task<AdminEndpointResult> CreateClassAsync(
        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 snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        if (!snapshot.Schools.Any(item => item.Id == user.SchoolId && item.Active && item.IsSourceSchool))
            return Error(409, "当前学校未设置为已启用的生源校");

        var name = Clean(Text(body["name"]), 100);
        var grade = Clean(Text(body["grade"]), 60);
        if (name.Length == 0 || grade.Length == 0) return Error(400, "年级和班级名称不能为空");
        if (snapshot.Classes.Any(item => item.SchoolId == user.SchoolId && item.Name == name))
            return Error(409, "本校已存在同名班级");

        var schoolClass = new AdminClass(Uid("class"), user.SchoolId!, name, grade, !ExactlyFalse(body["active"]));
        await repository.SaveClassAsync(
            schoolClass,
            isNew: true,
            Audit(user, "新增本校班级", $"{grade} · {name}"),
            cancellationToken);
        return Result(201, new JsonObject { ["ok"] = true, ["schoolClass"] = ClassJson(schoolClass) });
    }

    public async Task<AdminEndpointResult> UpdateClassAsync(
        string sessionToken,
        string classId,
        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 snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var existing = snapshot.Classes.FirstOrDefault(item => item.Id == classId && item.SchoolId == user.SchoolId);
        if (existing is null) return Error(404, "班级不存在");

        var name = Clean(NullishText(body, "name", existing.Name), 100);
        var grade = Clean(NullishText(body, "grade", existing.Grade), 60);
        if (name.Length == 0 || grade.Length == 0) return Error(400, "年级和班级名称不能为空");
        if (snapshot.Classes.Any(item => item.Id != existing.Id && item.SchoolId == user.SchoolId && item.Name == name))
            return Error(409, "本校已存在同名班级");

        var schoolClass = existing with
        {
            Name = name,
            Grade = grade,
            Active = NullishBoolean(body, "active", existing.Active)
        };
        await repository.SaveClassAsync(
            schoolClass,
            isNew: false,
            Audit(user, "更新本校班级", $"{grade} · {name} · {(schoolClass.Active ? "启用" : "停用")}"),
            cancellationToken);
        return Success(new JsonObject { ["ok"] = true, ["schoolClass"] = ClassJson(schoolClass) });
    }

    public async Task<AdminEndpointResult> CreateAdminAsync(
        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!;
        var username = Clean(Text(body["username"]), 50);
        var password = TruthyText(body["password"]);
        var displayName = Clean(Text(body["displayName"]), 50);
        var adminLevel = Level(user) == "school" ? "class" : Clean(Text(body["adminLevel"]), 20);
        if (Level(user) is not ("super" or "school")) return Error(403, "当前账号不能创建管理员");
        if (username.Length == 0 || displayName.Length == 0 || password.Length < 8 ||
            adminLevel is not ("super" or "school" or "class"))
            return Error(400, "请完整填写管理员账号、姓名、层级和至少 8 位密码");

        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        if (snapshot.Users.Any(item => string.Equals(item.Username, username, StringComparison.OrdinalIgnoreCase)))
            return Error(409, "该登录账号已存在");
        var schoolId = adminLevel == "super"
            ? null
            : Level(user) == "school" ? user.SchoolId : Clean(Text(body["schoolId"]), 64);
        var classId = adminLevel == "class" ? Clean(Text(body["classId"]), 64) : null;
        if (adminLevel != "super" && !snapshot.Schools.Any(item => item.Id == schoolId && item.Active && item.IsSourceSchool))
            return Error(400, "校级和班级管理员必须绑定已启用的生源校");
        if (adminLevel == "class" && !snapshot.Classes.Any(item => item.Id == classId && item.SchoolId == schoolId))
            return Error(400, "请选择该学校下的有效班级");

        var created = new AdminUser(
            Uid("usr"), username, "admin", adminLevel, schoolId, classId, displayName, null,
            true, false, false, null, NowIso());
        await repository.CreateAdminAsync(
            created,
            passwords.Hash(password),
            Audit(user, "创建管理员", $"{displayName} · {LevelNames[adminLevel]}"),
            cancellationToken);
        return Result(201, new JsonObject { ["ok"] = true, ["admin"] = SafeUser(created) });
    }

    public async Task<AdminEndpointResult> UpdateAdminAsync(
        string sessionToken,
        string adminId,
        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) is not ("super" or "school")) return Error(403, "当前账号不能维护管理员");
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var target = snapshot.Users.FirstOrDefault(item => item.Id == adminId && item.Role == "admin" &&
            (Level(user) == "super" || item.AdminLevel == "class" && item.SchoolId == user.SchoolId));
        if (target is null) return Error(404, "管理员账户不存在或不在当前管理范围");
        if (target.Id == user.Id && ExactlyFalse(body["active"]))
            return Error(409, "不能停用当前正在使用的管理员账户");

        var requestedClassId = Clean(TruthyText(body["classId"], target.ClassId ?? string.Empty), 64);
        var schoolClass = target.AdminLevel == "class"
            ? snapshot.Classes.FirstOrDefault(item => item.Id == requestedClassId && item.SchoolId == target.SchoolId)
            : null;
        if (target.AdminLevel == "class" && schoolClass is null)
            return Error(400, "请选择该管理员所属学校的有效班级");
        var password = TruthyText(body["password"]);
        if (password.Length is > 0 and < 8) return Error(400, "重置密码至少 8 位");
        var displayName = Clean(TruthyText(body["displayName"], target.DisplayName), 50);
        var active = NullishBoolean(body, "active", target.Active);
        var updated = target with
        {
            DisplayName = displayName,
            ClassId = schoolClass?.Id ?? target.ClassId,
            Active = active
        };
        await repository.UpdateAdminAsync(
            updated.Id,
            updated.DisplayName,
            updated.ClassId,
            updated.Active,
            password.Length == 0 ? null : passwords.Hash(password),
            Audit(user, "维护管理员账户", $"{updated.DisplayName} · {LevelNames[updated.AdminLevel ?? "super"]} · {(active ? "启用" : "停用")}"),
            cancellationToken);
        if (!active || password.Length > 0) await authenticationState.DeleteUserSessionsAsync(updated.Id);
        return Success(new JsonObject { ["ok"] = true, ["admin"] = SafeUser(updated) });
    }

    public async Task<AdminEndpointResult> ResetAdminPasswordAsync(
        string sessionToken,
        string adminId,
        CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        if (Level(user) is not ("super" or "school")) return Error(403, "当前账号不能重置管理员密码");
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var target = snapshot.Users.FirstOrDefault(item => item.Id == adminId && item.Role == "admin" &&
            (Level(user) == "super" || item.AdminLevel == "class" && item.SchoolId == user.SchoolId));
        if (target is null) return Error(404, "管理员账户不存在或不在当前管理范围");
        if (target.Id == user.Id) return Error(409, "当前账号请在“账户安全”中修改自己的密码");

        var temporaryPassword = $"Reset-{Base64Url(RandomNumberGenerator.GetBytes(7))}";
        await repository.UpdateAdminAsync(
            target.Id,
            target.DisplayName,
            target.ClassId,
            active: true,
            passwords.Hash(temporaryPassword),
            Audit(user, "重置管理员密码", $"{target.DisplayName} · {target.Username}"),
            cancellationToken);
        await authenticationState.DeleteUserSessionsAsync(target.Id);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["username"] = target.Username,
            ["temporaryPassword"] = temporaryPassword
        });
    }

    public async Task<AdminEndpointResult> UpdateSelfRegistrationAsync(
        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) != "super") return Error(403, "当前管理员层级无权执行此操作");
        var enabled = JsBoolean(body["enabled"]);
        await repository.UpdateSelfRegistrationAsync(
            enabled,
            Audit(
                user,
                enabled ? "开启自主注册" : "关闭自主注册",
                enabled ? "考生可从公开入口申请报名号" : "仅允许使用学校下发的报名号登录"),
            cancellationToken);
        return Success(new JsonObject { ["ok"] = true, ["enabled"] = enabled });
    }

    private async Task<ResolvedAdmin> ResolveAsync(string sessionToken, CancellationToken cancellationToken)
    {
        if (sessionToken.Length == 0) return ResolvedAdmin.Failed(Error(401, "请先登录"));
        var userId = await authenticationState.GetSessionUserIdAsync(sessionToken);
        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 ResolvedAdmin(user, null)
            : ResolvedAdmin.Failed(Error(403, "当前账号无权执行此操作"));
    }

    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 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 AdminAuditEntry Audit(AuthenticationUser user, string action, string detail) =>
        new(Uid("log"), user.Id, action, detail, NowIso());

    private static string NullishText(JsonObject body, string property, string fallback) =>
        body[property] is null ? fallback : Text(body[property]);

    private static bool NullishBoolean(JsonObject body, string property, bool fallback) =>
        body[property] is null ? fallback : JsBoolean(body[property]);

    private static bool ExactlyFalse(JsonNode? node) =>
        node is JsonValue value && value.TryGetValue<bool>(out var boolean) && !boolean;

    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 TruthyText(JsonNode? node, string fallback = "") =>
        JsBoolean(node) ? Text(node) : fallback;

    private static string Text(JsonNode? node)
    {
        if (node is null) return string.Empty;
        if (node is JsonValue value)
        {
            if (value.TryGetValue<string>(out var text)) return text;
            if (value.TryGetValue<bool>(out var boolean)) return boolean ? "true" : "false";
            if (value.TryGetValue<double>(out var number)) return number.ToString(CultureInfo.InvariantCulture);
        }
        return node.ToJsonString();
    }

    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 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 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 AdminEndpointResult Success(JsonObject body) => Result(200, body);

    private static AdminEndpointResult Result(int statusCode, JsonObject body) => new(statusCode, body);

    private static AdminEndpointResult Error(int status, string message) =>
        Result(status, new JsonObject { ["ok"] = false, ["message"] = message });

    [GeneratedRegex("^[A-Z0-9_-]+$", RegexOptions.CultureInvariant)]
    private static partial Regex SchoolCodePattern();

    private sealed record ResolvedAdmin(AuthenticationUser? User, AdminEndpointResult? Error)
    {
        public static ResolvedAdmin Failed(AdminEndpointResult error) => new(null, error);
    }
}
