using Microsoft.AspNetCore.Identity;
using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Enums;
using TakeoutSaaS.Domain.Identity.Repositories;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.Identity.Services;

/// <summary>
/// 管理后台认证服务实现。
/// </summary>
public sealed class AdminAuthService(
    IIdentityUserRepository userRepository,
    IUserRoleRepository userRoleRepository,
    IRoleRepository roleRepository,
    IPermissionRepository permissionRepository,
    IRolePermissionRepository rolePermissionRepository,
    IMenuRepository menuRepository,
    ITenantRepository tenantRepository,
    ITenantPackageRepository tenantPackageRepository,
    IPasswordHasher<IdentityUser> passwordHasher,
    IJwtTokenService jwtTokenService,
    IRefreshTokenStore refreshTokenStore,
    ITenantProvider tenantProvider) : IAdminAuthService
{
    private const string TenantAdminRoleCode = "tenant-admin";

    /// <summary>
    /// 管理后台登录：验证账号密码并生成令牌。
    /// </summary>
    /// <param name="request">登录请求</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>令牌响应</returns>
    /// <exception cref="BusinessException">账号或密码错误时抛出</exception>
    public async Task<TokenResponse> LoginAsync(AdminLoginRequest request, CancellationToken cancellationToken = default)
    {
        // 0. 强制要求租户上下文（严格多租户隔离）
        var currentTenantId = tenantProvider.GetCurrentTenantId();
        if (currentTenantId <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识，请使用 账号@手机号 登录或在 Header 指定租户");
        }

        // 1. 根据账号查找用户
        var user = await userRepository.FindByAccountAsync(currentTenantId, request.Account, cancellationToken)
                   ?? throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误");

        // 2. 校验账号状态
        var now = DateTime.UtcNow;
        if (user.Status == IdentityUserStatus.Disabled)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "账号已被禁用，请联系管理员");
        }
        if (user.Status == IdentityUserStatus.Locked)
        {
            if (user.LockedUntil.HasValue && user.LockedUntil.Value <= now)
            {
                await ResetLockedUserAsync(user.Id, cancellationToken);
            }
            else
            {
                throw new BusinessException(ErrorCodes.Forbidden, "账号已被锁定，请稍后再试");
            }
        }
        if (user.MustChangePassword)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "账号需要重置密码，请通过重置链接设置新密码");
        }

        // 3. 验证密码（使用 ASP.NET Core Identity 的密码哈希器）
        var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, request.Password);
        if (result == PasswordVerificationResult.Failed)
        {
            await IncreaseFailedLoginAsync(user.Id, now, cancellationToken);
            throw new BusinessException(ErrorCodes.Unauthorized, "账号或密码错误");
        }

        // 4. 更新登录成功状态
        await UpdateLoginSuccessAsync(user.Id, now, cancellationToken);

        // 5. 构建用户档案并生成令牌
        var profile = await BuildProfileAsync(user, cancellationToken);
        return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken);
    }

    /// <summary>
    /// 刷新访问令牌：使用刷新令牌获取新的访问令牌和刷新令牌。
    /// </summary>
    /// <param name="request">刷新令牌请求</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>新的令牌响应</returns>
    /// <exception cref="BusinessException">刷新令牌无效、已过期或用户不存在时抛出</exception>
    public async Task<TokenResponse> RefreshTokenAsync(RefreshTokenRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 验证刷新令牌（检查是否存在、是否过期、是否已撤销）
        var descriptor = await refreshTokenStore.GetAsync(request.RefreshToken, cancellationToken);
        if (descriptor == null || descriptor.ExpiresAt <= DateTime.UtcNow || descriptor.Revoked)
        {
            throw new BusinessException(ErrorCodes.Unauthorized, "RefreshToken 无效或已过期");
        }

        // 2. 根据用户 ID 查找用户
        var user = await userRepository.FindByIdAsync(descriptor.UserId, cancellationToken)
                   ?? throw new BusinessException(ErrorCodes.Unauthorized, "用户不存在");

        // 2.1 校验租户上下文与用户租户一致
        EnsureTenantMatched(user.TenantId);

        // 3. 撤销旧刷新令牌（防止重复使用）
        await refreshTokenStore.RevokeAsync(descriptor.Token, cancellationToken);

        // 4. 生成新的令牌对
        var profile = await BuildProfileAsync(user, cancellationToken);
        return await jwtTokenService.CreateTokensAsync(profile, false, cancellationToken);
    }

    /// <summary>
    /// 获取用户档案。
    /// </summary>
    /// <param name="userId">用户 ID</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>用户档案</returns>
    /// <exception cref="BusinessException">用户不存在时抛出</exception>
    public async Task<CurrentUserProfile> GetProfileAsync(long userId, CancellationToken cancellationToken = default)
    {
        var user = await userRepository.FindByIdAsync(userId, cancellationToken)
                   ?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在");
        // 1. 返回档案
        return await BuildProfileAsync(user, cancellationToken);
    }

    /// <summary>
    /// 获取当前用户可见菜单树。
    /// </summary>
    /// <param name="userId">用户 ID。</param>
    /// <param name="cancellationToken">取消令牌。</param>
    /// <returns>菜单树。</returns>
    public async Task<IReadOnlyList<MenuNodeDto>> GetMenuTreeAsync(long userId, CancellationToken cancellationToken = default)
    {
        // 1. 读取档案以获取权限
        var profile = await GetProfileAsync(userId, cancellationToken);

        // 2. 读取租户端菜单定义
        var definitions = await menuRepository.GetByPortalAsync(PortalType.Tenant, cancellationToken);

        // 3. 生成菜单树
        var menu = BuildMenuTree(definitions, profile.Permissions);
        return menu;
    }

    /// <summary>
    /// 获取指定用户的权限概览（校验当前租户）。
    /// </summary>
    public async Task<UserPermissionDto?> GetUserPermissionsAsync(long userId, CancellationToken cancellationToken = default)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        var user = await userRepository.FindByIdAsync(userId, cancellationToken);
        if (user == null || user.TenantId != tenantId)
        {
            return null;
        }

        var roleCodes = await ResolveUserRolesAsync(tenantId, user.Id, cancellationToken);
        var permissionCodes = await ResolveUserPermissionsAsync(tenantId, user.Id, cancellationToken);

        return new UserPermissionDto
        {
            UserId = user.Id,
            TenantId = user.TenantId,
            MerchantId = user.MerchantId,
            Account = user.Account,
            DisplayName = user.DisplayName,
            Roles = roleCodes,
            Permissions = permissionCodes,
            CreatedAt = user.CreatedAt
        };
    }

    /// <summary>
    /// 按租户分页查询用户权限概览。
    /// </summary>
    public async Task<PagedResult<UserPermissionDto>> SearchUserPermissionsAsync(
        string? keyword,
        int page,
        int pageSize,
        string? sortBy,
        bool sortDescending,
        CancellationToken cancellationToken = default)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        var users = await userRepository.SearchAsync(tenantId, keyword, cancellationToken);

        var sorted = sortBy?.ToLowerInvariant() switch
        {
            "account" => sortDescending
                ? users.OrderByDescending(x => x.Account)
                : users.OrderBy(x => x.Account),
            "displayname" => sortDescending
                ? users.OrderByDescending(x => x.DisplayName)
                : users.OrderBy(x => x.DisplayName),
            _ => sortDescending
                ? users.OrderByDescending(x => x.CreatedAt)
                : users.OrderBy(x => x.CreatedAt)
        };

        var paged = sorted
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToList();

        var resolved = await ResolveRolesAndPermissionsAsync(tenantId, paged, cancellationToken);
        var items = paged.Select(user => new UserPermissionDto
        {
            UserId = user.Id,
            TenantId = user.TenantId,
            MerchantId = user.MerchantId,
            Account = user.Account,
            DisplayName = user.DisplayName,
            Roles = resolved[user.Id].roles,
            Permissions = resolved[user.Id].permissions,
            CreatedAt = user.CreatedAt
        }).ToList();

        return new PagedResult<UserPermissionDto>(items, page, pageSize, users.Count);
    }

    private async Task<CurrentUserProfile> BuildProfileAsync(IdentityUser user, CancellationToken cancellationToken)
    {
        // 1. 读取用户角色
        var tenantId = user.TenantId;
        var roles = await ResolveUserRolesAsync(tenantId, user.Id, cancellationToken);

        // 2. 强制仅允许租户管理员登录（不支持超级管理员）
        EnsureTenantAdmin(tenantId, roles);

        // 3. 加载权限
        var permissions = await ResolveUserPermissionsAsync(tenantId, user.Id, cancellationToken);

        // 4. 读取当前套餐名称（严格以数据库为准）
        var packageName = await ResolveTenantPackageNameAsync(tenantId, cancellationToken);

        // 5. 返回用户档案
        return new CurrentUserProfile
        {
            UserId = user.Id,
            Account = user.Account,
            DisplayName = user.DisplayName,
            TenantId = user.TenantId,
            MerchantId = user.MerchantId,
            Roles = roles,
            Permissions = permissions,
            Avatar = user.Avatar,
            PackageName = packageName
        };
    }

    private async Task<string?> ResolveTenantPackageNameAsync(long tenantId, CancellationToken cancellationToken)
    {
        // 1. 查询当前有效订阅
        var subscription = await tenantRepository.GetActiveSubscriptionAsync(tenantId, cancellationToken);
        if (subscription == null)
        {
            return null;
        }

        // 2. 查询订阅关联套餐
        var package = await tenantPackageRepository.FindByIdAsync(subscription.TenantPackageId, cancellationToken);
        if (package == null)
        {
            return null;
        }

        // 3. 返回套餐名称
        return package.Name;
    }

    private void EnsureTenantMatched(long userTenantId)
    {
        // 1. 读取当前租户
        var currentTenantId = tenantProvider.GetCurrentTenantId();
        if (currentTenantId <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识，请在 Header 指定租户");
        }

        // 2. 校验租户一致
        if (currentTenantId != userTenantId)
        {
            throw new BusinessException(ErrorCodes.Unauthorized, "RefreshToken 无效或已过期");
        }
    }

    private static void EnsureTenantAdmin(long tenantId, IReadOnlyCollection<string> roles)
    {
        // 1. 租户 ID 必须有效
        if (tenantId <= 0)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "仅允许租户管理员登录");
        }

        // 2. 必须具备租户管理员角色
        var isTenantAdmin = roles.Any(role => string.Equals(role, TenantAdminRoleCode, StringComparison.OrdinalIgnoreCase));
        if (!isTenantAdmin)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "仅允许租户管理员登录");
        }
    }

    private async Task ResetLockedUserAsync(long userId, CancellationToken cancellationToken)
    {
        // 1. 获取可更新实体
        var tracked = await userRepository.GetForUpdateAsync(userId, cancellationToken);
        if (tracked == null)
        {
            return;
        }

        // 2. 解除锁定并清空失败次数
        tracked.Status = IdentityUserStatus.Active;
        tracked.LockedUntil = null;
        tracked.FailedLoginCount = 0;

        // 3. 保存变更
        await userRepository.SaveChangesAsync(cancellationToken);
    }

    private async Task IncreaseFailedLoginAsync(long userId, DateTime now, CancellationToken cancellationToken)
    {
        // 1. 获取可更新实体
        var tracked = await userRepository.GetForUpdateAsync(userId, cancellationToken);
        if (tracked == null)
        {
            return;
        }

        // 2. 累计失败次数并判断是否需要锁定
        tracked.FailedLoginCount += 1;
        if (tracked.FailedLoginCount >= 5)
        {
            tracked.Status = IdentityUserStatus.Locked;
            tracked.LockedUntil = now.AddMinutes(15);
        }

        // 3. 保存变更
        await userRepository.SaveChangesAsync(cancellationToken);
    }

    private async Task UpdateLoginSuccessAsync(long userId, DateTime now, CancellationToken cancellationToken)
    {
        // 1. 获取可更新实体
        var tracked = await userRepository.GetForUpdateAsync(userId, cancellationToken);
        if (tracked == null)
        {
            return;
        }

        // 2. 重置失败次数并刷新登录时间
        tracked.FailedLoginCount = 0;
        tracked.LockedUntil = null;
        tracked.LastLoginAt = now;
        if (tracked.Status == IdentityUserStatus.Locked)
        {
            tracked.Status = IdentityUserStatus.Active;
        }

        // 3. 保存变更
        await userRepository.SaveChangesAsync(cancellationToken);
    }

    private static IReadOnlyList<MenuNodeDto> BuildMenuTree(
        IReadOnlyList<Domain.Identity.Entities.MenuDefinition> definitions,
        IReadOnlyList<string> permissions)
    {
        // 1. 权限集合
        var permissionSet = new HashSet<string>(permissions ?? [], StringComparer.OrdinalIgnoreCase);

        // 2. 父子分组
        var parentLookup = definitions
            .GroupBy(x => x.ParentId)
            .ToDictionary(g => g.Key, g => g.OrderBy(d => d.SortOrder).ToList());

        // 3. 递归构造并过滤
        IReadOnlyList<MenuNodeDto> BuildChildren(long parentId)
        {
            if (!parentLookup.TryGetValue(parentId, out var children))
            {
                return [];
            }

            // 1. 收集可见节点
            var result = new List<MenuNodeDto>();
            foreach (var def in children)
            {
                // 1.1 构造节点
                var node = MapMenuNode(def);

                // 1.2 递归子节点
                var sub = BuildChildren(def.Id);

                // 1.3 可见性
                var required = node.RequiredPermissions ?? [];
                if (required.Length == 0 && node.Meta.Permissions.Length > 0)
                {
                    // Fall back to meta permissions when explicit required permissions are missing.
                    required = node.Meta.Permissions;
                }

                var visible = required.Length == 0 || required.Any(permissionSet.Contains);

                // 1.4 收集
                if (visible || sub.Count > 0)
                {
                    result.Add(node with { Children = sub });
                }
            }

            // 2. 返回本层节点
            return result;
        }

        // 4. 返回根节点
        return BuildChildren(0);
    }

    private static MenuNodeDto MapMenuNode(Domain.Identity.Entities.MenuDefinition definition)
    {
        // 1. 解析权限
        var requiredPermissions = SplitCodes(definition.RequiredPermissions);
        var metaPermissions = SplitCodes(definition.MetaPermissions);
        var metaRoles = SplitCodes(definition.MetaRoles);

        // 2. 解析按钮权限
        var authList = string.IsNullOrWhiteSpace(definition.AuthListJson)
            ? []
            : System.Text.Json.JsonSerializer.Deserialize<List<MenuAuthItemDto>>(definition.AuthListJson) ?? [];

        // 3. 构造节点
        return new MenuNodeDto
        {
            Name = definition.Name,
            Path = definition.Path,
            Component = definition.Component,
            Meta = new MenuMetaDto
            {
                Title = definition.Title,
                Icon = definition.Icon,
                KeepAlive = definition.KeepAlive,
                IsIframe = definition.IsIframe,
                Link = definition.Link,
                Roles = metaRoles,
                Permissions = metaPermissions,
                AuthList = authList
            },
            Children = [],
            RequiredPermissions = requiredPermissions
        };
    }

    private static string[] SplitCodes(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return Array.Empty<string>();
        }

        // 1. 拆分去重
        return value
            .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToArray();
    }

    private async Task<string[]> ResolveUserRolesAsync(long tenantId, long userId, CancellationToken cancellationToken)
    {
        var relations = await userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken);
        var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray();
        if (roleIds.Length == 0)
        {
            return Array.Empty<string>();
        }

        var roles = await roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken);
        return roles.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
    }

    private async Task<string[]> ResolveUserPermissionsAsync(long tenantId, long userId, CancellationToken cancellationToken)
    {
        var relations = await userRoleRepository.GetByUserIdAsync(tenantId, userId, cancellationToken);
        var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray();
        if (roleIds.Length == 0)
        {
            return Array.Empty<string>();
        }

        var rolePermissions = await rolePermissionRepository.GetByRoleIdsAsync(tenantId, roleIds, cancellationToken);
        var permissionIds = rolePermissions.Select(x => x.PermissionId).Distinct().ToArray();
        if (permissionIds.Length == 0)
        {
            return Array.Empty<string>();
        }

        var permissions = await permissionRepository.GetByIdsAsync(tenantId, permissionIds, cancellationToken);
        return permissions.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
    }

    private async Task<Dictionary<long, (string[] roles, string[] permissions)>> ResolveRolesAndPermissionsAsync(
        long tenantId,
        IReadOnlyCollection<IdentityUser> users,
        CancellationToken cancellationToken)
    {
        var userIds = users.Select(x => x.Id).ToArray();
        var userRoleRelations = await userRoleRepository.GetByUserIdsAsync(tenantId, userIds, cancellationToken);
        var roleIds = userRoleRelations.Select(x => x.RoleId).Distinct().ToArray();

        var roles = roleIds.Length == 0
            ? Array.Empty<Role>()
            : await roleRepository.GetByIdsAsync(tenantId, roleIds, cancellationToken);
        var roleCodeMap = roles.ToDictionary(r => r.Id, r => r.Code, comparer: EqualityComparer<long>.Default);

        var rolePermissions = roleIds.Length == 0
            ? Array.Empty<RolePermission>()
            : await rolePermissionRepository.GetByRoleIdsAsync(tenantId, roleIds, cancellationToken);

        var permissionIds = rolePermissions.Select(x => x.PermissionId).Distinct().ToArray();
        var permissions = permissionIds.Length == 0
            ? Array.Empty<Permission>()
            : await permissionRepository.GetByIdsAsync(tenantId, permissionIds, cancellationToken);
        var permissionCodeMap = permissions.ToDictionary(p => p.Id, p => p.Code, comparer: EqualityComparer<long>.Default);

        var rolePermissionsLookup = rolePermissions
            .GroupBy(rp => rp.RoleId)
            .ToDictionary(g => g.Key, g => g.Select(rp => rp.PermissionId).ToArray(), comparer: EqualityComparer<long>.Default);

        var result = new Dictionary<long, (string[] roles, string[] permissions)>();
        foreach (var userId in userIds)
        {
            var rolesForUser = userRoleRelations.Where(ur => ur.UserId == userId).Select(ur => ur.RoleId).Distinct().ToArray();
            var roleCodes = rolesForUser
                .Select(rid => roleCodeMap.GetValueOrDefault(rid))
                .Where(c => !string.IsNullOrWhiteSpace(c))
                .Select(c => c!)
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToArray();

            var permissionCodes = rolesForUser
                .SelectMany(rid => rolePermissionsLookup.GetValueOrDefault(rid) ?? Array.Empty<long>())
                .Select(pid => permissionCodeMap.GetValueOrDefault(pid))
                .Where(code => !string.IsNullOrWhiteSpace(code))
                .Select(code => code!)
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToArray();

            result[userId] = (roleCodes, permissionCodes);
        }

        return result;
    }
}
