using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using MiGu.Server.Configs;

namespace MiGu.Server.Auth;

/// <summary>
/// RBAC 权威存储：用户 + 角色 + 权限页面授权，内存态 + <c>data/rbac.json</c> 文件持久化。
///
/// 取代旧的硬编码 <c>UserStore</c> + <c>AuthController.BuildPermissions</c>：
/// - 登录密码校验、角色解析、有效权限（页面 / 操作 / 控件）全部由本类计算；
/// - 管理端「权限与角色」页通过 <c>RbacController</c> 增删改用户 / 角色，落盘后即时生效；
/// - 密码以 PBKDF2-SHA256(100k, 16B salt) 哈希存储，比较走 FixedTimeEquals 防时序攻击。
///
/// 首次启动（rbac.json 不存在）时 seed 两个内置账号：
///   admin（超级管理员，scope=*，全部页面 / 操作）
///   ops  （运营人员，scope=RCSMonitor，运营四页 + 运维操作码）
/// 初始密码取 appsettings <c>Auth:Users:{name}:Password</c>，缺省 admin/ops（开发弱口令，生产须改）。
/// </summary>
public sealed class RbacStore
{
    public sealed record EffectiveResult(List<string> Pages, List<string> Ops, List<WidgetGrantDto> Widgets);

    private const string RoleAdminId = "role-admin";
    private const string RoleOpsId = "role-ops";

    private readonly object _gate = new();
    private readonly string _file;
    private readonly ILogger<RbacStore> _logger;
    private readonly JsonSerializerOptions _jsonOpts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = true
    };

    private RbacSnapshot _snapshot = new();

    public RbacStore(IConfiguration config, IWebHostEnvironment env, ILogger<RbacStore> logger)
    {
        _logger = logger;
        var dataDir = Path.Combine(env.ContentRootPath, "data");
        Directory.CreateDirectory(dataDir);
        _file = Path.Combine(dataDir, "rbac.json");
        Load(config);
    }

    // ───────────────────────── 加载 / 持久化 ─────────────────────────

    private void Load(IConfiguration config)
    {
        if (File.Exists(_file))
        {
            try
            {
                var json = File.ReadAllText(_file);
                var snap = JsonSerializer.Deserialize<RbacSnapshot>(json, _jsonOpts);
                if (snap is { Users.Count: > 0 })
                {
                    _snapshot = Normalize(snap);
                    _logger.LogInformation("RBAC 从 {File} 载入：{Users} 用户 / {Roles} 角色。",
                        _file, _snapshot.Users.Count, _snapshot.Roles.Count);
                    return;
                }
                _logger.LogWarning("RBAC 文件 {File} 内容为空或无用户，回退到默认 seed。", _file);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "RBAC 文件 {File} 解析失败，回退到默认 seed。", _file);
            }
        }

        _snapshot = SeedDefault(config);
        Persist();
        _logger.LogInformation("RBAC 已生成默认数据并写入 {File}（admin / ops）。", _file);
    }

    /// <summary>清洗加载结果：补默认、去重、过滤非法页面 Key，保证内置角色存在。</summary>
    private static RbacSnapshot Normalize(RbacSnapshot snap)
    {
        snap.Roles ??= new();
        snap.Users ??= new();
        foreach (var r in snap.Roles)
        {
            r.Pages = (r.Pages ?? new()).Where(p => p == PageCatalog.Wildcard || PageCatalog.IsValidKey(p)).Distinct().ToList();
            r.Ops = (r.Ops ?? new()).Distinct().ToList();
            r.WidgetGrants ??= new();
            if (string.IsNullOrWhiteSpace(r.Scope)) r.Scope = PageCatalog.ScopePlatform;
        }
        foreach (var u in snap.Users)
        {
            u.RoleIds = (u.RoleIds ?? new()).Distinct().ToList();
        }
        return snap;
    }

    private RbacSnapshot SeedDefault(IConfiguration config)
    {
        var adminPwd = config["Auth:Users:admin:Password"] ?? "admin";
        var opsPwd = config["Auth:Users:ops:Password"] ?? "ops";
        if (adminPwd == "admin" || opsPwd == "ops")
            _logger.LogWarning("RBAC seed 使用默认弱密码（admin/ops），生产环境请尽快在「权限与角色」页修改或通过环境变量覆盖。");

        var snap = new RbacSnapshot
        {
            Version = 1,
            Roles = new List<RbacRole>
            {
                new()
                {
                    Id = RoleAdminId, Name = "超级管理员", Description = "拥有全部页面与操作权限的内置角色",
                    Scope = PageCatalog.Wildcard,
                    Pages = new() { PageCatalog.Wildcard },
                    Ops = new() { "*" },
                    WidgetGrants = new(),
                    System = true
                },
                new()
                {
                    Id = RoleOpsId, Name = "运营人员", Description = "运营监控端默认角色：可执行运维操作、查看监控",
                    Scope = PageCatalog.ScopeMonitor,
                    Pages = new() { "monitor-dashboard", "monitor-map", "monitor-ops", "monitor-notes" },
                    Ops = new()
                    {
                        "ops.car.pause", "ops.car.resume", "ops.car.gohome", "ops.car.resetSession",
                        "ops.car.manualCharge", "ops.task.pause", "ops.task.cancel", "ops.task.reassign",
                        "ops.task.boostPriority", "monitor.note.write"
                    },
                    WidgetGrants = new()
                    {
                        new("MapEditor", "readonly"),
                        new("CadToolbar", "hidden"),
                        new("CarPanel", "readonly"),
                        new("MissionEditor", "readonly"),
                        new("OpsActionPanel", "interactive"),
                        new("ConfigCenter", "hidden")
                    },
                    System = true
                }
            },
            Users = new List<RbacUser>()
        };

        snap.Users.Add(NewUser("u-admin", "admin", "系统管理员", adminPwd, new() { RoleAdminId }));
        snap.Users.Add(NewUser("u-ops", "ops", "运营人员", opsPwd, new() { RoleOpsId }));
        return snap;
    }

    private void Persist()
    {
        try
        {
            File.WriteAllText(_file, JsonSerializer.Serialize(_snapshot, _jsonOpts));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "RBAC 持久化到 {File} 失败。", _file);
        }
    }

    // ───────────────────────── 登录 / 鉴权读取 ─────────────────────────

    /// <summary>用户名 + 密码校验。返回 null = 不存在 / 已禁用 / 密码错（不区分原因，防用户名枚举）。</summary>
    public RbacUser? VerifyCredentials(string username, string password)
    {
        if (string.IsNullOrWhiteSpace(username) || string.IsNullOrEmpty(password)) return null;
        lock (_gate)
        {
            var u = FindByName(username);
            if (u is null || !u.Enabled) return null;
            if (!VerifyHash(password, u.Salt, u.PasswordHash)) return null;
            return Clone(u);
        }
    }

    public RbacUser? FindUser(string username)
    {
        lock (_gate) { var u = FindByName(username); return u is null ? null : Clone(u); }
    }

    /// <summary>当前用户可登录的 scope 集合（其角色覆盖的 scope，<c>*</c> 角色覆盖全部）。</summary>
    public List<string> UsableScopes(RbacUser user)
    {
        lock (_gate)
        {
            var roles = RolesOf(user);
            var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (var r in roles)
            {
                if (r.Scope == PageCatalog.Wildcard)
                {
                    set.Add(PageCatalog.ScopePlatform);
                    set.Add(PageCatalog.ScopeMonitor);
                }
                else set.Add(r.Scope);
            }
            return set.ToList();
        }
    }

    public bool CanUseScope(RbacUser user, string scope) =>
        UsableScopes(user).Contains(scope, StringComparer.OrdinalIgnoreCase);

    /// <summary>角色名（展示用，写入 AuthUserDto.Roles / JWT role claim）。</summary>
    public List<string> RoleNamesOf(RbacUser user)
    {
        lock (_gate) { return RolesOf(user).Select(r => r.Name).ToList(); }
    }

    /// <summary>
    /// 计算用户在指定 scope 下的有效权限：可访问页面、操作码、控件可见性，均取适用角色的并集。
    /// 适用角色 = 角色 scope 等于该 scope，或角色 scope 为通配 <c>*</c>。
    /// </summary>
    public EffectiveResult ComputeEffective(RbacUser user, string scope)
    {
        lock (_gate)
        {
            var roles = RolesOf(user).Where(r => r.Scope == PageCatalog.Wildcard
                || string.Equals(r.Scope, scope, StringComparison.OrdinalIgnoreCase)).ToList();

            var scopeKeys = PageCatalog.KeysForScope(scope).ToHashSet(StringComparer.OrdinalIgnoreCase);
            var pages = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            var ops = new HashSet<string>(StringComparer.Ordinal);
            var allOps = false;
            var bestWidget = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            foreach (var r in roles)
            {
                if (r.Pages.Contains(PageCatalog.Wildcard)) pages.UnionWith(scopeKeys);
                else foreach (var p in r.Pages) if (scopeKeys.Contains(p)) pages.Add(p);

                foreach (var o in r.Ops)
                {
                    if (o == "*") allOps = true;
                    else ops.Add(o);
                }

                foreach (var g in r.WidgetGrants)
                {
                    if (!bestWidget.TryGetValue(g.WidgetId, out var cur) || Rank(g.Visibility) > Rank(cur))
                        bestWidget[g.WidgetId] = g.Visibility;
                }
            }

            return new EffectiveResult(
                pages.OrderBy(p => p, StringComparer.Ordinal).ToList(),
                allOps ? new List<string> { "*" } : ops.OrderBy(o => o, StringComparer.Ordinal).ToList(),
                bestWidget.Select(kv => new WidgetGrantDto(kv.Key, kv.Value)).ToList());
        }
    }

    // ───────────────────────── 管理端读取 ─────────────────────────

    public List<RbacRole> ListRoles()
    {
        lock (_gate) { return _snapshot.Roles.Select(Clone).ToList(); }
    }

    public List<RbacUserDto> ListUsers()
    {
        lock (_gate) { return _snapshot.Users.Select(ToDto).ToList(); }
    }

    // ───────────────────────── 用户 CRUD ─────────────────────────

    public RbacUserDto CreateUser(CreateUserRequest req)
    {
        if (string.IsNullOrWhiteSpace(req.Username)) throw new RbacException("用户名不能为空");
        if (string.IsNullOrEmpty(req.Password)) throw new RbacException("初始密码不能为空");
        lock (_gate)
        {
            if (FindByName(req.Username) is not null) throw new RbacException($"用户名 {req.Username} 已存在");
            var roleIds = FilterExistingRoles(req.RoleIds);
            var user = NewUser($"u-{NewId()}", req.Username.Trim(),
                string.IsNullOrWhiteSpace(req.DisplayName) ? req.Username.Trim() : req.DisplayName!.Trim(),
                req.Password, roleIds);
            user.Enabled = req.Enabled;
            _snapshot.Users.Add(user);
            Persist();
            return ToDto(user);
        }
    }

    public RbacUserDto UpdateUser(string id, UpdateUserRequest req)
    {
        lock (_gate)
        {
            var u = _snapshot.Users.FirstOrDefault(x => x.Id == id) ?? throw new RbacException("用户不存在");
            if (req.DisplayName is not null) u.DisplayName = req.DisplayName.Trim();
            if (req.RoleIds is not null) u.RoleIds = FilterExistingRoles(req.RoleIds);
            if (req.Enabled is bool en) u.Enabled = en;
            Persist();
            return ToDto(u);
        }
    }

    public void SetPassword(string id, string password)
    {
        if (string.IsNullOrEmpty(password)) throw new RbacException("密码不能为空");
        lock (_gate)
        {
            var u = _snapshot.Users.FirstOrDefault(x => x.Id == id) ?? throw new RbacException("用户不存在");
            var salt = RandomNumberGenerator.GetBytes(16);
            u.Salt = Convert.ToBase64String(salt);
            u.PasswordHash = Convert.ToBase64String(Pbkdf2(password, salt));
            Persist();
        }
    }

    public void DeleteUser(string id)
    {
        lock (_gate)
        {
            var u = _snapshot.Users.FirstOrDefault(x => x.Id == id) ?? throw new RbacException("用户不存在");
            _snapshot.Users.Remove(u);
            Persist();
        }
    }

    // ───────────────────────── 角色 CRUD ─────────────────────────

    public RbacRole CreateRole(SaveRoleRequest req)
    {
        if (string.IsNullOrWhiteSpace(req.Name)) throw new RbacException("角色名称不能为空");
        var scope = NormalizeScope(req.Scope);
        lock (_gate)
        {
            var role = new RbacRole
            {
                Id = $"role-{NewId()}",
                Name = req.Name.Trim(),
                Description = req.Description?.Trim() ?? "",
                Scope = scope,
                Pages = SanitizePages(req.Pages),
                Ops = req.Ops?.Distinct().ToList() ?? new(),
                WidgetGrants = req.WidgetGrants ?? new(),
                System = false
            };
            _snapshot.Roles.Add(role);
            Persist();
            return Clone(role);
        }
    }

    public RbacRole UpdateRole(string id, SaveRoleRequest req)
    {
        if (string.IsNullOrWhiteSpace(req.Name)) throw new RbacException("角色名称不能为空");
        var scope = NormalizeScope(req.Scope);
        lock (_gate)
        {
            var role = _snapshot.Roles.FirstOrDefault(r => r.Id == id) ?? throw new RbacException("角色不存在");
            role.Name = req.Name.Trim();
            role.Description = req.Description?.Trim() ?? "";
            role.Scope = scope;
            role.Pages = SanitizePages(req.Pages);
            role.Ops = req.Ops?.Distinct().ToList() ?? new();
            role.WidgetGrants = req.WidgetGrants ?? new();
            Persist();
            return Clone(role);
        }
    }

    public void DeleteRole(string id)
    {
        lock (_gate)
        {
            var role = _snapshot.Roles.FirstOrDefault(r => r.Id == id) ?? throw new RbacException("角色不存在");
            if (role.System) throw new RbacException("内置系统角色不可删除");
            var inUse = _snapshot.Users.Where(u => u.RoleIds.Contains(id)).Select(u => u.Username).ToList();
            if (inUse.Count > 0)
                throw new RbacException($"角色仍被 {inUse.Count} 个用户使用（{string.Join(", ", inUse.Take(5))}{(inUse.Count > 5 ? "…" : "")}），请先解除关联");
            _snapshot.Roles.Remove(role);
            Persist();
        }
    }

    public bool RoleExists(string id)
    {
        lock (_gate) { return _snapshot.Roles.Any(r => r.Id == id); }
    }

    // ───────────────────────── 内部工具 ─────────────────────────

    private RbacUser? FindByName(string username) =>
        _snapshot.Users.FirstOrDefault(u => string.Equals(u.Username, username, StringComparison.OrdinalIgnoreCase));

    private List<RbacRole> RolesOf(RbacUser user) =>
        user.RoleIds.Select(id => _snapshot.Roles.FirstOrDefault(r => r.Id == id))
                    .Where(r => r is not null).Select(r => r!).ToList();

    private List<string> FilterExistingRoles(List<string>? roleIds) =>
        (roleIds ?? new()).Where(id => _snapshot.Roles.Any(r => r.Id == id)).Distinct().ToList();

    private static List<string> SanitizePages(List<string>? pages)
    {
        if (pages is null) return new();
        if (pages.Contains(PageCatalog.Wildcard)) return new() { PageCatalog.Wildcard };
        return pages.Where(PageCatalog.IsValidKey).Distinct().ToList();
    }

    private static string NormalizeScope(string? scope) => scope switch
    {
        PageCatalog.ScopePlatform => PageCatalog.ScopePlatform,
        PageCatalog.ScopeMonitor => PageCatalog.ScopeMonitor,
        PageCatalog.Wildcard => PageCatalog.Wildcard,
        _ => throw new RbacException($"无效 scope: {scope}（应为 Platform / RCSMonitor / *）")
    };

    private static int Rank(string visibility) => visibility switch
    {
        "interactive" => 2,
        "readonly" => 1,
        _ => 0
    };

    private static RbacUser NewUser(string id, string username, string displayName, string password, List<string> roleIds)
    {
        var salt = RandomNumberGenerator.GetBytes(16);
        return new RbacUser
        {
            Id = id,
            Username = username,
            DisplayName = displayName,
            Enabled = true,
            RoleIds = roleIds,
            Salt = Convert.ToBase64String(salt),
            PasswordHash = Convert.ToBase64String(Pbkdf2(password, salt))
        };
    }

    private RbacUserDto ToDto(RbacUser u) =>
        new(u.Id, u.Username, u.DisplayName, u.Enabled, new List<string>(u.RoleIds), UsableScopesNoLock(u));

    private List<string> UsableScopesNoLock(RbacUser user)
    {
        var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var r in RolesOf(user))
        {
            if (r.Scope == PageCatalog.Wildcard) { set.Add(PageCatalog.ScopePlatform); set.Add(PageCatalog.ScopeMonitor); }
            else set.Add(r.Scope);
        }
        return set.ToList();
    }

    private static bool VerifyHash(string password, string saltB64, string hashB64)
    {
        try
        {
            var salt = Convert.FromBase64String(saltB64);
            var expected = Convert.FromBase64String(hashB64);
            var actual = Pbkdf2(password, salt);
            return CryptographicOperations.FixedTimeEquals(actual, expected);
        }
        catch { return false; }
    }

    private static byte[] Pbkdf2(string password, byte[] salt) =>
        Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, iterations: 100_000, HashAlgorithmName.SHA256, 32);

    private static string NewId() => Guid.NewGuid().ToString("N")[..8];

    private static RbacUser Clone(RbacUser u) => new()
    {
        Id = u.Id, Username = u.Username, DisplayName = u.DisplayName, Enabled = u.Enabled,
        RoleIds = new List<string>(u.RoleIds), Salt = u.Salt, PasswordHash = u.PasswordHash
    };

    private static RbacRole Clone(RbacRole r) => new()
    {
        Id = r.Id, Name = r.Name, Description = r.Description, Scope = r.Scope,
        Pages = new List<string>(r.Pages), Ops = new List<string>(r.Ops),
        WidgetGrants = r.WidgetGrants.Select(w => new WidgetGrantDto(w.WidgetId, w.Visibility)).ToList(),
        System = r.System
    };
}

/// <summary>RBAC 业务校验异常 —— 由 <c>RbacController</c> 统一翻译成 400 + message。</summary>
public sealed class RbacException : Exception
{
    public RbacException(string message) : base(message) { }
}
