using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using MiGu.Server.Auth;
using MiGu.Server.Persistence;

namespace MiGu.Server.Dashboard;

public sealed class DashboardShortcutService
{
    private readonly PlatformDbContext _db;
    private readonly RbacStore _rbac;

    private static readonly JsonSerializerOptions JsonOpts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };

    public DashboardShortcutService(PlatformDbContext db, RbacStore rbac)
    {
        _db = db;
        _rbac = rbac;
    }

    public sealed record QuickEntriesResult(IReadOnlyList<string> Keys, bool UsingDefaults);

    public async Task<QuickEntriesResult> GetAsync(string userId, string scope, CancellationToken ct = default)
    {
        scope = NormalizeScope(scope);
        var allowed = AllowedPages(userId, scope);

        var row = await _db.UserDashboardShortcuts
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.UserId == userId && x.Scope == scope, ct);

        if (row == null)
        {
            var defaults = EnsureMandatoryKeys(
                FilterKeys(DashboardShortcutCatalog.DefaultKeysForScope(scope), scope, allowed),
                scope, allowed);
            return new QuickEntriesResult(
                defaults.Take(DashboardShortcutCatalog.MaxKeysPerUser).ToList(),
                UsingDefaults: true);
        }

        var keys = ParseKeys(row.KeysJson);
        var filtered = EnsureMandatoryKeys(
            FilterKeys(keys, scope, allowed), scope, allowed)
            .Take(DashboardShortcutCatalog.MaxKeysPerUser)
            .ToList();
        return new QuickEntriesResult(filtered, UsingDefaults: false);
    }

    public async Task<QuickEntriesResult> SaveAsync(
        string userId, string scope, IReadOnlyList<string>? keys, CancellationToken ct = default)
    {
        scope = NormalizeScope(scope);
        var allowed = AllowedPages(userId, scope);
        var sanitized = EnsureMandatoryKeys(
            FilterKeys(Deduplicate(keys ?? []), scope, allowed), scope, allowed);
        if (sanitized.Count > DashboardShortcutCatalog.MaxKeysPerUser)
            sanitized = sanitized.Take(DashboardShortcutCatalog.MaxKeysPerUser).ToList();

        var row = await _db.UserDashboardShortcuts
            .FirstOrDefaultAsync(x => x.UserId == userId && x.Scope == scope, ct);

        var json = JsonSerializer.Serialize(sanitized, JsonOpts);
        var now = DateTimeOffset.UtcNow;

        if (row == null)
        {
            _db.UserDashboardShortcuts.Add(new UserDashboardShortcut
            {
                UserId = userId,
                Scope = scope,
                KeysJson = json,
                UpdatedAt = now
            });
        }
        else
        {
            row.KeysJson = json;
            row.UpdatedAt = now;
        }

        await _db.SaveChangesAsync(ct);
        return new QuickEntriesResult(sanitized, UsingDefaults: false);
    }

    private HashSet<string> AllowedPages(string userId, string scope)
    {
        var user = _rbac.FindUserById(userId);
        if (user == null || !user.Enabled)
            return new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        var eff = _rbac.ComputeEffective(user, scope);
        return eff.Pages.ToHashSet(StringComparer.OrdinalIgnoreCase);
    }

    private static List<string> FilterKeys(
        IEnumerable<string> keys, string scope, HashSet<string> allowedPages)
    {
        var outKeys = new List<string>();
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var raw in keys)
        {
            var key = DashboardShortcutCatalog.NormalizeKey(raw ?? "");
            if (string.IsNullOrEmpty(key)) continue;
            if (!DashboardShortcutCatalog.IsValidKey(key)) continue;
            if (!DashboardShortcutCatalog.KeyMatchesScope(key, scope)) continue;
            if (seen.Contains(key)) continue;

            var pageKey = DashboardShortcutCatalog.PageKeyFor(key);
            if (!allowedPages.Contains(pageKey)) continue;

            seen.Add(key);
            outKeys.Add(key);
        }
        return outKeys;
    }

    private static List<string> EnsureMandatoryKeys(
        List<string> keys, string scope, HashSet<string> allowedPages)
    {
        if (!string.Equals(scope, PageCatalog.ScopePlatform, StringComparison.OrdinalIgnoreCase))
            return keys;

        var result = new List<string>();
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        foreach (var raw in DashboardShortcutCatalog.MandatoryPlatformKeys)
        {
            var key = DashboardShortcutCatalog.NormalizeKey(raw);
            if (!DashboardShortcutCatalog.IsValidKey(key)) continue;
            var pageKey = DashboardShortcutCatalog.PageKeyFor(key);
            if (!allowedPages.Contains(pageKey)) continue;
            if (seen.Add(key)) result.Add(key);
        }

        foreach (var key in keys)
        {
            if (seen.Add(key)) result.Add(key);
        }

        return result;
    }

    private static List<string> Deduplicate(IReadOnlyList<string> keys)
    {
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var list = new List<string>();
        foreach (var k in keys)
        {
            if (string.IsNullOrWhiteSpace(k)) continue;
            var t = k.Trim();
            if (seen.Add(t)) list.Add(t);
        }
        return list;
    }

    private static List<string> ParseKeys(string json)
    {
        try
        {
            return JsonSerializer.Deserialize<List<string>>(json, JsonOpts) ?? [];
        }
        catch
        {
            return [];
        }
    }

    private static string NormalizeScope(string scope) =>
        string.Equals(scope, PageCatalog.ScopeMonitor, StringComparison.OrdinalIgnoreCase)
            ? PageCatalog.ScopeMonitor
            : PageCatalog.ScopePlatform;
}
