using Microsoft.AspNetCore.Http;
using System.Net;
using TakeoutSaaS.Application.Identity.Abstractions;
using TakeoutSaaS.Application.Identity.Contracts;
using TakeoutSaaS.Domain.Identity.Entities;
using TakeoutSaaS.Domain.Identity.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.Identity.Services;

/// <summary>
/// 小程序认证服务实现。
/// </summary>
public sealed class MiniAuthService(
    IWeChatAuthService weChatAuthService,
    IMiniUserRepository miniUserRepository,
    IJwtTokenService jwtTokenService,
    IRefreshTokenStore refreshTokenStore,
    ILoginRateLimiter rateLimiter,
    IHttpContextAccessor httpContextAccessor,
    ITenantProvider tenantProvider) : IMiniAuthService
{
    /// <summary>
    /// 微信小程序登录：通过微信 code 获取用户信息并生成令牌。
    /// </summary>
    /// <param name="request">微信登录请求</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>令牌响应</returns>
    /// <exception cref="BusinessException">获取微信用户信息失败、缺少租户标识时抛出</exception>
    public async Task<TokenResponse> LoginWithWeChatAsync(WeChatLoginRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 限流检查（基于 IP 地址）
        var throttleKey = BuildThrottleKey();
        await rateLimiter.EnsureAllowedAsync(throttleKey, cancellationToken);

        // 2. 通过微信 code 获取 session（OpenId、UnionId、SessionKey）
        var session = await weChatAuthService.Code2SessionAsync(request.Code, cancellationToken);
        if (string.IsNullOrWhiteSpace(session.OpenId))
        {
            throw new BusinessException(ErrorCodes.Unauthorized, "获取微信用户信息失败");
        }

        // 3. 获取当前租户 ID（多租户支持）
        var tenantId = tenantProvider.GetCurrentTenantId();
        if (tenantId == 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识");
        }

        // 4. 获取或创建小程序用户（如果 OpenId 已存在则返回现有用户，否则创建新用户）
        var (user, isNew) = await GetOrBindMiniUserAsync(session.OpenId, session.UnionId, request.Nickname, request.Avatar, tenantId, cancellationToken);

        // 5. 登录成功后重置限流计数
        await rateLimiter.ResetAsync(throttleKey, cancellationToken);

        // 6. 构建用户档案并生成令牌
        var profile = BuildProfile(user);
        return await jwtTokenService.CreateTokensAsync(profile, isNew, 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 miniUserRepository.FindByIdAsync(descriptor.UserId, cancellationToken)
                   ?? throw new BusinessException(ErrorCodes.Unauthorized, "用户不存在");

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

        // 4. 生成新的令牌对
        var profile = BuildProfile(user);
        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 miniUserRepository.FindByIdAsync(userId, cancellationToken)
                   ?? throw new BusinessException(ErrorCodes.NotFound, "用户不存在");

        return BuildProfile(user);
    }

    /// <summary>
    /// 获取或绑定小程序用户：如果 OpenId 已存在则返回现有用户，否则创建新用户。
    /// </summary>
    /// <param name="openId">微信 OpenId</param>
    /// <param name="unionId">微信 UnionId（可选）</param>
    /// <param name="nickname">昵称</param>
    /// <param name="avatar">头像地址（可选）</param>
    /// <param name="tenantId">租户 ID</param>
    /// <param name="cancellationToken">取消令牌</param>
    /// <returns>用户实体和是否为新用户的元组</returns>
    private async Task<(MiniUser user, bool isNew)> GetOrBindMiniUserAsync(string openId, string? unionId, string? nickname, string? avatar, long tenantId, CancellationToken cancellationToken)
    {
        // 检查用户是否已存在
        var existing = await miniUserRepository.FindByOpenIdAsync(openId, cancellationToken);
        if (existing != null)
        {
            return (existing, false);
        }

        // 创建新用户
        var created = await miniUserRepository.CreateOrUpdateAsync(openId, unionId, nickname, avatar, tenantId, cancellationToken);
        return (created, true);
    }

    private static CurrentUserProfile BuildProfile(MiniUser user)
        => new()
        {
            UserId = user.Id,
            Account = user.OpenId,
            DisplayName = user.Nickname,
            TenantId = user.TenantId,
            MerchantId = null,
            Roles = Array.Empty<string>(),
            Permissions = Array.Empty<string>(),
            Avatar = user.Avatar
        };

    private string BuildThrottleKey()
    {
        var ip = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress ?? IPAddress.Loopback;
        return $"mini-login:{ip}";
    }
}
