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

namespace Eis.Infrastructure.Administration;

internal sealed class AdminNoticeService(
    IAuthenticationStateStore authenticationState,
    AuthenticationRepository authenticationRepository,
    AdminNoticeRepository repository,
    NoticeContentFormatter formatter) : IAdminNoticeService
{
    public async Task<AdminEndpointResult> ListAsync(string sessionToken, CancellationToken cancellationToken)
    {
        var context = await ResolveSuperAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var snapshot = await repository.LoadManagementAsync(cancellationToken);
        var notices = snapshot.Notices
            .OrderByDescending(item => ParseDate(item.PublishAt ?? item.CreatedAt))
            .Select(NoticeJson)
            .ToArray();
        return Success(new JsonObject
        {
            ["ok"] = true,
            ["notices"] = new JsonArray(notices),
            ["publications"] = new JsonArray(AdminPublicationProjector.Build(snapshot).ToArray())
        });
    }

    public async Task<AdminEndpointResult> CreateAsync(
        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 title = Clean(Text(body["title"]), 120);
        var content = formatter.Sanitize(Text(body["content"]));
        var contentText = formatter.PlainText(content);
        if (title.Length == 0 || contentText.Length == 0) return Error(400, "通知标题和正文不能为空");
        var summary = Clean(Text(body["summary"]), 260);
        var category = Clean(Text(body["category"]), 30);
        var draft = Text(body["status"]) == "draft";
        var notice = new AdminNotice(
            Uid("notice"),
            title,
            summary.Length == 0 ? contentText[..Math.Min(contentText.Length, 80)] : summary,
            content,
            category.Length == 0 ? "通知公告" : category,
            JsBoolean(body["pinned"]),
            draft ? "draft" : "published",
            draft ? null : NowIso(),
            NowIso(),
            user.DisplayName);
        await repository.CreateAsync(
            notice,
            Audit(user, draft ? "保存通知草稿" : "发布通知", notice.Title),
            cancellationToken);
        return new AdminEndpointResult(201, new JsonObject { ["ok"] = true, ["notice"] = NoticeJson(notice) });
    }

    public async Task<AdminEndpointResult> UpdateAsync(
        string sessionToken,
        string noticeId,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveSuperAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var existing = await repository.FindAsync(noticeId, cancellationToken);
        if (existing is null) return Error(404, "通知不存在");
        var notice = existing with
        {
            Title = body["title"] is null ? existing.Title : Clean(Text(body["title"]), 260),
            Summary = body["summary"] is null ? existing.Summary : Clean(Text(body["summary"]), 260),
            Category = body["category"] is null ? existing.Category : Clean(Text(body["category"]), 260),
            Pinned = body["pinned"] is null ? existing.Pinned : JsBoolean(body["pinned"])
        };
        if (body["content"] is not null)
        {
            var content = formatter.Sanitize(Text(body["content"]));
            if (formatter.PlainText(content).Length == 0) return Error(400, "通知正文不能为空");
            notice = notice with { Content = content };
        }
        var requestedStatus = Text(body["status"]);
        if (requestedStatus is "draft" or "published")
        {
            notice = notice with
            {
                Status = requestedStatus,
                PublishAt = requestedStatus == "published" && notice.PublishAt is null ? NowIso() : notice.PublishAt
            };
        }
        await repository.UpdateAsync(
            notice,
            Audit(user, "更新通知", $"{notice.Title} · {notice.Status}"),
            cancellationToken);
        return Success(new JsonObject { ["ok"] = true, ["notice"] = NoticeJson(notice) });
    }

    public async Task<AdminEndpointResult> UpdatePublicationVisibilityAsync(
        string sessionToken,
        string sourceType,
        string publicationId,
        JsonObject body,
        CancellationToken cancellationToken)
    {
        var context = await ResolveSuperAsync(sessionToken, cancellationToken);
        if (context.Error is not null) return context.Error;
        var user = context.User!;
        var snapshot = await repository.LoadManagementAsync(cancellationToken);
        var existing = AdminPublicationProjector.FindSourceRecord(snapshot.Records, sourceType, publicationId);
        if (existing is null) return Error(404, "系统公示不存在");
        if (body["visible"] is not JsonValue value || !value.TryGetValue<bool>(out var visible))
        {
            return Error(400, "请明确设置是否显示");
        }
        var payload = existing.Payload.DeepClone().AsObject();
        payload["publicVisible"] = visible;
        var updated = existing with { Payload = payload, UpdatedAt = NowIso() };
        await repository.UpdatePublicationAsync(
            updated,
            Audit(user, visible ? "显示系统公示" : "隐藏系统公示", $"{sourceType} · {updated.Id}"),
            cancellationToken);

        var updatedRecords = snapshot.Records.Select(item => item.Id == updated.Id ? updated : item).ToArray();
        var updatedSnapshot = snapshot with { Records = updatedRecords };
        var publication = AdminPublicationProjector.Build(updatedSnapshot)
            .FirstOrDefault(item => item["id"]?.GetValue<string>() == publicationId &&
                                    item["sourceType"]?.GetValue<string>() == sourceType);
        var response = new JsonObject { ["ok"] = true };
        if (publication is not null) response["publication"] = publication;
        return Success(response);
    }

    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 JsonObject NoticeJson(AdminNotice item) => new()
    {
        ["id"] = item.Id,
        ["title"] = item.Title,
        ["summary"] = item.Summary,
        ["content"] = formatter.Sanitize(item.Content),
        ["category"] = item.Category,
        ["pinned"] = item.Pinned,
        ["status"] = item.Status,
        ["publishAt"] = JsonValue.Create(item.PublishAt),
        ["createdAt"] = JsonValue.Create(item.CreatedAt),
        ["author"] = item.Author,
        ["contentHtml"] = formatter.ContentHtml(item.Content)
    };

    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) =>
        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 DateTimeOffset ParseDate(string? value) =>
        DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed) ? parsed : DateTimeOffset.MinValue;
    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); }
}
