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

namespace Eis.Infrastructure.Administration;

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

    private static readonly IReadOnlyDictionary<string, string[]> Permissions = new Dictionary<string, string[]>(StringComparer.Ordinal)
    {
        ["super"] = ["*"],
        ["school"] =
        [
            "dashboard.read", "candidates.read", "candidates.write", "candidates.review", "registrations.read",
            "registrations.review", "payments.read", "payments.write", "results.read", "centers.read", "centers.write",
            "workflows.inbox"
        ],
        ["class"] =
        [
            "dashboard.read", "candidates.read", "candidates.review", "registrations.read", "registrations.review",
            "payments.read", "payments.write", "results.read", "workflows.inbox"
        ]
    };

    public async Task<AdminEndpointResult> GetContextAsync(string sessionToken, CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var level = Level(context.User!);
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["admin"] = SafeUser(context.User!),
            ["adminLevelName"] = LevelNames[level],
            ["permissions"] = StringArray(Permissions[level]),
            ["scopeLabel"] = ScopeLabel(snapshot, context.User!),
            ["schools"] = new JsonArray(snapshot.Schools.Select(SchoolJson).ToArray()),
            ["classes"] = new JsonArray(snapshot.Classes.Select(ClassJson).ToArray())
        });
    }

    public async Task<AdminEndpointResult> GetDashboardAsync(string sessionToken, CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var profiles = snapshot.Profiles.Where(item => InScope(user, item.SchoolId, item.ClassId)).ToArray();
        var userIds = profiles.Select(item => item.UserId).ToHashSet(StringComparer.Ordinal);
        var registrations = snapshot.Registrations.Where(item => userIds.Contains(item.UserId)).ToArray();
        var visibleFlows = snapshot.WorkflowInstances.Where(instance =>
        {
            if (Level(user) == "super") return true;
            var scope = WorkflowScope(snapshot, instance);
            return scope is not null && InScope(user, scope.Value.SchoolId, scope.Value.ClassId) &&
                (instance.AssigneeId == user.Id || instance.Status != "pending");
        }).ToArray();
        var logs = snapshot.AuditLogs
            .Where(item => Level(user) == "super" || item.ActorId == user.Id)
            .Take(8)
            .Select(LogJson)
            .ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["admin"] = SafeUser(user),
            ["scopeLabel"] = ScopeLabel(snapshot, user),
            ["permissions"] = StringArray(Permissions[Level(user)]),
            ["metrics"] = new JsonObject
            {
                ["candidates"] = profiles.Length,
                ["pendingCandidates"] = profiles.Count(item => item.Status == "pending"),
                ["registrations"] = registrations.Length,
                ["pendingRegistrations"] = registrations.Count(item => item.Status == "pending"),
                ["pendingPayments"] = registrations.Count(item => item.Status == "approved" && item.PaymentStatus == "unpaid"),
                ["pendingFlows"] = visibleFlows.Count(item => item.Status == "pending"),
                ["publishedExams"] = snapshot.Exams.Count(item => item.Status == "published"),
                ["notices"] = snapshot.PublishedNoticeCount
            },
            ["logs"] = new JsonArray(logs)
        });
    }

    public async Task<AdminEndpointResult> GetSchoolsAsync(string sessionToken, CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        if (Level(context.User!) != "super") return Error(403, "只有超级管理员可以管理学校");
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var schools = snapshot.Schools.Select(school =>
        {
            var item = SchoolJson(school);
            item["classCount"] = snapshot.Classes.Count(entry => entry.SchoolId == school.Id);
            item["adminCount"] = snapshot.Users.Count(entry => entry.Role == "admin" && entry.SchoolId == school.Id);
            item["candidateCount"] = snapshot.Profiles.Count(entry => entry.SchoolId == school.Id);
            item["centerCount"] = snapshot.CenterSchoolIds.Count(id => id == school.Id);
            return item;
        }).ToArray();
        return Success(new JsonObject { ["ok"] = true, ["schools"] = new JsonArray(schools) });
    }

    public async Task<AdminEndpointResult> GetSchoolOrganizationAsync(string sessionToken, 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 school = snapshot.Schools.FirstOrDefault(item => item.Id == user.SchoolId);
        var classes = snapshot.Classes.Where(item => item.SchoolId == user.SchoolId).Select(item =>
        {
            var output = ClassJson(item);
            output["candidateCount"] = snapshot.Profiles.Count(profile => profile.ClassId == item.Id);
            output["admins"] = new JsonArray(snapshot.Users
                .Where(admin => admin.Role == "admin" && admin.AdminLevel == "class" && admin.ClassId == item.Id)
                .Select(admin =>
                {
                    var json = SafeUser(admin);
                    json["active"] = admin.Active;
                    return json;
                }).ToArray());
            return output;
        }).ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["school"] = school is null ? null : SchoolJson(school),
            ["classes"] = new JsonArray(classes)
        });
    }

    public async Task<AdminEndpointResult> GetAdminsAsync(string sessionToken, 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 admins = snapshot.Users.Where(item => item.Role == "admin" &&
            (Level(user) == "super" || item.AdminLevel == "class" && item.SchoolId == user.SchoolId)).Select(item =>
        {
            var output = SafeUser(item);
            output["active"] = item.Active;
            output["levelName"] = LevelNames[item.AdminLevel ?? "super"];
            output["schoolName"] = snapshot.Schools.FirstOrDefault(school => school.Id == item.SchoolId)?.Name ?? string.Empty;
            output["className"] = snapshot.Classes.FirstOrDefault(schoolClass => schoolClass.Id == item.ClassId)?.Name ?? string.Empty;
            return output;
        }).ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["admins"] = new JsonArray(admins),
            ["schools"] = new JsonArray(snapshot.Schools.Where(item => item.IsSourceSchool).Select(SchoolJson).ToArray()),
            ["classes"] = new JsonArray(snapshot.Classes.Select(ClassJson).ToArray()),
            ["selfRegistrationEnabled"] = snapshot.SelfRegistrationEnabled
        });
    }

    public async Task<AdminEndpointResult> GetExamsAsync(string sessionToken, CancellationToken cancellationToken)
    {
        var context = await ResolveAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        if (Level(context.User!) != "super") return Error(403, "当前管理员层级无权执行此操作");
        var snapshot = await snapshotLoader.LoadAsync(cancellationToken);
        var exams = snapshot.Exams.Select(exam =>
        {
            var output = PublicExamJson(exam);
            output["registrationCount"] = snapshot.Registrations.Count(item => item.ExamId == exam.Id);
            return output;
        }).ToArray();
        return Success(new JsonObject { ["ok"] = true, ["exams"] = new JsonArray(exams) });
    }

    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 (string? SchoolId, string? ClassId)? WorkflowScope(AdminReadSnapshot snapshot, AdminWorkflowInstance instance)
    {
        if (instance.BusinessType == "profile_change")
        {
            var profile = snapshot.Profiles.FirstOrDefault(item => item.Id == instance.BusinessId);
            return profile is null ? null : (profile.SchoolId, profile.ClassId);
        }
        if (instance.BusinessType == "registration_review")
        {
            var registration = snapshot.Registrations.FirstOrDefault(item => item.Id == instance.BusinessId);
            var profile = snapshot.Profiles.FirstOrDefault(item => item.UserId == registration?.UserId);
            return profile is null ? null : (profile.SchoolId, profile.ClassId);
        }
        if (instance.BusinessType == "center_change")
        {
            var change = snapshot.CenterChanges.FirstOrDefault(item => item.Id == instance.BusinessId);
            return change is null ? null : (change.SchoolId, null);
        }
        if (instance.BusinessType == "candidate_account_batch")
        {
            var batch = snapshot.AccountBatches.FirstOrDefault(item => item.Id == instance.BusinessId);
            return batch is null ? null : (batch.SchoolId, null);
        }
        if (instance.BusinessType == "score_appeal")
        {
            var result = snapshot.Results.FirstOrDefault(item => item.Id == instance.BusinessId);
            var registration = snapshot.Registrations.FirstOrDefault(item => item.Id == result?.RegistrationId);
            var profile = snapshot.Profiles.FirstOrDefault(item => item.UserId == registration?.UserId);
            return profile is null ? null : (profile.SchoolId, profile.ClassId);
        }
        return null;
    }

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

    private static string ScopeLabel(AdminReadSnapshot snapshot, AuthenticationUser user)
    {
        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 JsonObject PublicExamJson(AdminExam item)
    {
        var json = ExamJson(item);
        json["totalScore"] = item.Subjects.Sum(subject => subject.FullScore);
        json["registrationState"] = item.ArchivedAt is not null
            ? "archived"
            : DateTimeOffset.UtcNow < ParseDate(item.RegistrationStart) ? "upcoming"
            : DateTimeOffset.UtcNow > ParseDate(item.RegistrationEnd) ? "closed"
            : "open";
        return json;
    }

    private static JsonObject ExamJson(AdminExam item) => new()
    {
        ["id"] = item.Id,
        ["code"] = item.Code,
        ["name"] = item.Name,
        ["description"] = item.Description,
        ["registrationStart"] = item.RegistrationStart,
        ["registrationEnd"] = item.RegistrationEnd,
        ["examStart"] = item.ExamStart,
        ["examEnd"] = item.ExamEnd,
        ["admitDownloadStart"] = item.AdmitDownloadStart,
        ["admitDownloadEnd"] = item.AdmitDownloadEnd,
        ["location"] = item.Location,
        ["passPolicy"] = item.PassPolicy,
        ["passValue"] = item.PassValue,
        ["status"] = item.Status,
        ["archivedAt"] = JsonValue.Create(item.ArchivedAt),
        ["archivedBy"] = JsonValue.Create(item.ArchivedBy),
        ["createdAt"] = item.CreatedAt,
        ["subjects"] = new JsonArray(item.Subjects.Select(SubjectJson).ToArray())
    };

    private static JsonObject SubjectJson(AdminSubject item) => new()
    {
        ["id"] = item.Id,
        ["name"] = item.Name,
        ["date"] = item.Date,
        ["start"] = item.Start,
        ["end"] = item.End,
        ["fee"] = item.Fee,
        ["fullScore"] = item.FullScore,
        ["passRule"] = item.PassRule,
        ["passValue"] = item.PassValue,
        ["passScore"] = JsonValue.Create(item.PassScore),
        ["order"] = item.Order
    };

    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(AuthenticationUser 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 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 JsonObject LogJson(AdminAuditLog item) => new()
    {
        ["id"] = item.Id,
        ["actorId"] = JsonValue.Create(item.ActorId),
        ["action"] = item.Action,
        ["detail"] = item.Detail,
        ["createdAt"] = item.CreatedAt
    };

    private static JsonArray StringArray(IEnumerable<string> values) =>
        new(values.Select(value => JsonValue.Create(value)).ToArray());

    private static string Level(AuthenticationUser user) => user.AdminLevel ?? "super";

    private static DateTimeOffset ParseDate(string value) =>
        DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed)
            ? parsed
            : DateTimeOffset.MinValue;

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