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.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;

namespace TakeoutSaaS.Application.Identity.Services;

/// <summary>
/// 管理后台认证服务实现。
/// </summary>
public sealed class AdminAuthService(
    IIdentityUserRepository userRepository,
    IUserRoleRepository userRoleRepository,
    IRoleRepository roleRepository,
    IPermissionRepository permissionRepository,
    IRolePermissionRepository rolePermissionRepository,
    IMenuRepository menuRepository,
    IPasswordHasher<IdentityUser> passwordHasher,
    IJwtTokenService jwtTokenService,
    IRefreshTokenStore refreshTokenStore) : IAdminAuthService
{
    /// <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)
    {
        // 1. 根据账号查找用户
        var user = await userRepository.FindByAccountAsync(PortalType.Admin, null, 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>
    /// 简化登录：与标准登录一致（Admin Portal）。
    /// </summary>
    /// <param name="request">登录请求</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>令牌响应</returns>
    public async Task<TokenResponse> LoginSimpleAsync(AdminLoginRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 参数校验
        if (string.IsNullOrWhiteSpace(request.Account))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "账号不能为空");
        }

        // 2. (空行后) 标准化账号
        request.Account = request.Account.Trim();

        // 3. (空行后) 走标准登录逻辑（Admin Portal）
        return await LoginAsync(request, 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, "用户不存在");

        // 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(profile.Portal, cancellationToken);

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

    private async Task<CurrentUserProfile> BuildProfileAsync(IdentityUser user, CancellationToken cancellationToken)
    {
        // 1. 解析角色集合
        var roles = await ResolveUserRolesAsync(user.Portal, user.TenantId, user.Id, cancellationToken);
        // 2. (空行后) 解析权限集合
        var permissions = await ResolveUserPermissionsAsync(user.Portal, user.TenantId, user.Id, cancellationToken);

        // 3. (空行后) 组装档案
        return new CurrentUserProfile
        {
            Portal = user.Portal,
            UserId = user.Id,
            Account = user.Account,
            DisplayName = user.DisplayName,
            TenantId = user.TenantId,
            MerchantId = user.MerchantId,
            Roles = roles,
            Permissions = permissions,
            Avatar = user.Avatar
        };
    }

    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 bool IsLikelyPhone(string value)
    {
        // 1. 空值快速返回
        if (string.IsNullOrWhiteSpace(value))
        {
            return false;
        }

        // 2. 标准化前缀
        var span = value.AsSpan();
        if (span[0] == '+')
        {
            span = span[1..];
        }

        // 3. 长度校验
        if (span.Length < 6 || span.Length > 32)
        {
            return false;
        }

        // 4. 逐字符校验
        foreach (var ch in span)
        {
            if (!char.IsDigit(ch))
            {
                return false;
            }
        }

        // 5. 返回校验结果
        return true;
    }

    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 可见性（严格使用 RequiredPermissions，不做兜底）
                var requiredPermissions = node.RequiredPermissions;
                // 1.4 (空行后) 判断可见性
                var isVisible = requiredPermissions.Length == 0 || requiredPermissions.Any(permissionSet.Contains);

                // 1.5 (空行后) 收集
                if (isVisible || 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(PortalType portal, long? tenantId, long userId, CancellationToken cancellationToken)
    {
        var relations = await userRoleRepository.GetByUserIdAsync(portal, tenantId, userId, cancellationToken);
        var roleIds = relations.Select(x => x.RoleId).Distinct().ToArray();
        if (roleIds.Length == 0)
        {
            return Array.Empty<string>();
        }

        // 2. (空行后) 查询角色定义并返回角色码
        var roles = await roleRepository.GetByIdsAsync(portal, tenantId, roleIds, cancellationToken);
        return roles.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
    }

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

        // 2. (空行后) 查询角色权限关联
        var rolePermissions = await rolePermissionRepository.GetByRoleIdsAsync(portal, tenantId, roleIds, cancellationToken);
        var permissionIds = rolePermissions.Select(x => x.PermissionId).Distinct().ToArray();
        if (permissionIds.Length == 0)
        {
            return Array.Empty<string>();
        }

        // 3. (空行后) 查询权限定义并返回权限码
        var permissions = await permissionRepository.GetByIdsAsync(permissionIds, cancellationToken);
        return permissions.Select(x => x.Code).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
    }
}
