// iyixiGames.Application/Services/UserService.cs
using System;
using System.Security.Cryptography; // 用于 HMACSHA512 (密码哈希示例)
using System.Text;                   // 用于字符串编码（如 UTF8）
using System.Threading.Tasks;        // 用于支持异步操作（async/await）
using Microsoft.EntityFrameworkCore; // 引入 Entity Framework Core，用于数据库上下文操作（如查询、添加、保存）
using iyixiGames.Domain.DTOs.Auth; // 引入认证相关的数据传输对象（DTOs），如 LoginRequest 和 LoginResponse
using iyixiGames.Domain.Entities;   // 引入你的领域实体，例如 User 类
using iyixiGames.Infrastructure.Data; // 引用 Infrastructure 层来访问 ApplicationDbContext，实现数据持久化
using iyixiGames.Infrastructure.Services;
using Microsoft.Extensions.Logging;  // 引入 Microsoft 的日志记录接口，用于记录应用程序事件

namespace iyixiGames.Application.Services
{
    /// <summary>
    /// 用户服务实现类，负责处理用户相关的核心业务逻辑，如用户查询、密码验证、登录和注册。
    /// </summary>
    public class UserService : IUserService
    {
        private readonly ApplicationDbContext _context; // 数据库上下文实例，用于与数据库交互
        private readonly ILogger<UserService> _logger;  // 日志记录器实例，用于记录服务操作的日志信息

        /// <summary>
        /// UserService 类的构造函数。
        /// 通过依赖注入 (Dependency Injection) 获取 ApplicationDbContext 和 ILogger 实例。
        /// </summary>
        /// <param name="context">应用程序的数据库上下文，提供对用户数据的访问。</param>
        /// <param name="logger">日志记录器，用于记录服务执行过程中的信息、警告和错误。</param>
        public UserService(ApplicationDbContext context, ILogger<UserService> logger)
        {
            _context = context;
            _logger = logger;
        }

        /// <summary>
        /// 根据用户名异步获取用户实体。
        /// </summary>
        /// <param name="username">要查询的用户名。</param>
        /// <returns>
        /// 如果找到匹配的用户名且用户处于激活状态，则返回对应的 <see cref="User"/> 实体；
        /// 否则返回 null。
        /// </returns>
        public async Task<User?> GetUserByUsernameAsync(string username)
        {
            // 记录调试信息，表明正在尝试根据用户名查找用户
            _logger.LogDebug("尝试根据用户名 '{Username}' 获取用户。", username);

            // 使用 FirstOrDefaultAsync 异步查询数据库中的用户表，
            // 查找用户名匹配且 IsActive 为 true（即用户是激活状态）的第一个用户。
            return await _context.Users
                                 .FirstOrDefaultAsync(u => u.Username == username && u.IsActive);
        }

        /// <summary>
        /// 根据电子邮件地址异步获取用户实体。
        /// </summary>
        /// <param name="email">要查询的电子邮件地址。</param>
        /// <returns>
        /// 如果找到匹配的电子邮件地址，则返回对应的 <see cref="User"/> 实体；
        /// 否则返回 null。此方法不检查用户是否激活，主要用于邮箱唯一性检查。
        /// </returns>
        public async Task<User?> GetUserByEmailAsync(string email)
        {
            // 记录调试信息，表明正在尝试根据电子邮件获取用户
            _logger.LogDebug("尝试根据电子邮件 '{Email}' 获取用户。", email);

            // 使用 FirstOrDefaultAsync 异步查询数据库中的用户表，
            // 查找电子邮件地址匹配的第一个用户。
            return await _context.Users
                                 .FirstOrDefaultAsync(u => u.Email == email);
        }

        /// <summary>
        /// 验证给定用户的密码是否正确。
        /// </summary>
        /// <param name="user">要验证的用户实体，包含存储的哈希密码和盐值。</param>
        /// <param name="password">用户输入的明文密码。</param>
        /// <returns>如果输入的密码与存储的哈希匹配则为 true，否则为 false。</returns>
        /// <remarks>
        /// **！！！重要安全警告！！！**
        /// **当前实现中使用的 HMACSHA512 仅作为概念验证示例。**
        /// **在生产环境中，HMACSHA512 绝不应该用于存储用户密码。它是一种消息认证码，并非专门设计用于抵御密码破解攻击。**
        /// **请务必替换为更安全的密码哈希算法，如：**
        /// **1. PBKDF2 (通过 Microsoft.AspNetCore.Identity.PasswordHasher) - ASP.NET Core 推荐的默认方式。**
        /// **2. Argon2 (通过 Argon2.NET 或类似库) - 目前认为最安全的密码哈希算法之一，能有效抵御暴力破解和彩虹表攻击。**
        /// 这些算法具有更高的计算成本和内存消耗，并能抵抗 GPU 和 ASIC 攻击。
        /// </remarks>
        public async Task<bool> VerifyPasswordAsync(User user, string password)
        {
            // 早期退出：如果用户对象为空或输入的密码为空/空白，则直接返回验证失败。
            if (user == null || string.IsNullOrWhiteSpace(password))
            {
                _logger.LogWarning("密码验证失败：用户对象为 null 或输入的密码为空。");
                return false;
            }

            try
            {
                // 将存储在数据库中的 Base64 编码的盐值转换回字节数组。
                var saltBytes = Convert.FromBase64String(user.Salt);

                // 使用存储的盐值（作为密钥）初始化 HMACSHA512 实例。
                // 确保创建哈希和验证哈希时使用的盐是相同的。
                using (var hmac = new HMACSHA512(saltBytes))
                {
                    // 对用户输入的明文密码进行哈希计算。
                    // 同样，确保字符编码（如 UTF8）在整个过程中保持一致。
                    var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));

                    // 将存储在数据库中的 Base64 编码的哈希密码转换回字节数组。
                    var storedHash = Convert.FromBase64String(user.PasswordHash);

                    // 比较计算出的哈希和存储的哈希。
                    // 长度不一致直接判定为不匹配。
                    if (computedHash.Length != storedHash.Length)
                    {
                        _logger.LogDebug("密码验证失败：计算出的哈希长度与存储的哈希长度不匹配。");
                        return false;
                    }

                    // 逐字节比较两个哈希值。
                    // 这是一个简单的方式，但为了更好的安全性（抵抗时序攻击），
                    // 在生产环境中应使用 `CryptographicOperations.FixedTimeEquals`。
                    for (int i = 0; i < computedHash.Length; i++)
                    {
                        if (computedHash[i] != storedHash[i])
                        {
                            _logger.LogDebug("密码验证失败：哈希值字节不匹配。");
                            return false;
                        }
                    }
                }
                _logger.LogDebug("密码验证成功。"); // 记录成功日志（调试级别）
                return true; // 密码匹配，验证成功
            }
            catch (FormatException ex)
            {
                // 捕获 Base64 字符串格式错误的异常，通常是数据存储问题。
                _logger.LogError(ex, "密码验证失败：存储的 Base64 字符串格式错误。用户ID: {UserId}", user.Id);
                return false;
            }
            catch (Exception ex)
            {
                // 捕获其他任何未预期的异常。
                _logger.LogError(ex, "密码验证过程中发生未预期的错误。用户ID: {UserId}", user.Id);
                return false;
            }
        }

        /// <summary>
        /// 为给定的明文密码生成哈希值和随机盐值。
        /// </summary>
        /// <param name="password">要哈希的明文密码。</param>
        /// <returns>一个元组，包含 Base64 编码的哈希密码字符串和 Base64 编码的盐值字符串。</returns>
        /// <remarks>
        /// **！！！重要安全警告！！！**
        /// **当前实现中使用的 HMACSHA512 仅作为概念验证示例。**
        /// **在生产环境中，HMACSHA512 绝不应该用于存储用户密码。请务必替换为更安全的密码哈希算法。**
        /// </remarks>
        public async Task<(string HashedPassword, string Salt)> CreatePasswordHash(string password)
        {
            // 使用 HMACSHA512 类来生成密码哈希和盐。
            // HMACSHA512 的 Key 属性在每次实例化时都会生成一个加密安全的、随机的密钥。
            // 我们可以利用这个随机密钥作为密码哈希的“盐”。
            using (var hmac = new HMACSHA512())
            {
                var saltBytes = hmac.Key; // 获取 HMAC 实例生成的随机密钥作为盐的字节数组
                var salt = Convert.ToBase64String(saltBytes); // 将盐的字节数组转换为 Base64 字符串以便存储

                // 对明文密码进行哈希计算，使用 UTF8 编码将密码字符串转换为字节。
                var computedHashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
                var hashedPassword = Convert.ToBase64String(computedHashBytes); // 将哈希结果字节数组转换为 Base64 字符串

                // 返回包含哈希密码和盐的元组。
                return (hashedPassword, salt);
            }
        }

        /// <summary>
        /// 处理用户登录请求的核心业务逻辑。
        /// </summary>
        /// <param name="request">包含用户名和密码的登录请求数据传输对象。</param>
        /// <returns>一个 <see cref="LoginResponse"/> 对象，指示登录是否成功，并包含相关消息、用户ID和用户名。</returns>
        public async Task<LoginResponse> LoginAsync(LoginRequest request)
        {
            // 记录用户信息级别的日志，表明有用户尝试登录。
            _logger.LogInformation("用户 '{Username}' 尝试登录。", request.Username);

            // 根据用户名从数据库获取用户实体。
            // 注意：GetUserByUsernameAsync 仅返回激活的用户。
            var user = await GetUserByUsernameAsync(request.Username);

            // 检查用户是否存在（或是否激活）。
            if (user == null)
            {
                _logger.LogWarning("登录失败：用户名 '{Username}' 不存在或未激活。", request.Username);
                // 返回登录失败响应，提供通用的错误消息以避免泄露用户存在信息。
                return new LoginResponse { Success = false, Message = "用户名或密码错误。" };
            }

            // 验证用户输入的密码是否正确。
            var isPasswordValid = await VerifyPasswordAsync(user, request.Password);

            // 如果密码验证失败。
            if (!isPasswordValid)
            {
                _logger.LogWarning("登录失败：用户 '{Username}' 密码不匹配。", request.Username);
                // 返回登录失败响应，同样提供通用错误消息。
                return new LoginResponse { Success = false, Message = "用户名或密码错误。" };
            }

            // --- 登录成功处理 ---
            // 更新用户的最后登录时间为当前的 UTC 时间。
            user.LastLoginAt = DateTime.UtcNow;
            _context.Users.Update(user); // 将用户实体标记为已修改，Entity Framework Core 将追踪此更改。
            await _context.SaveChangesAsync(); // 将更改保存到数据库。

            _logger.LogInformation("用户 '{Username}' 登录成功，ID: {UserId}。", user.Username, user.Id);

            // 返回成功的登录响应，包含用户ID和用户名。
            // JWT token 的实际生成通常在更高层（如认证控制器或中间件）处理，
            // 因为它可能需要访问 HTTP 上下文或其他认证相关的服务。
            return new LoginResponse { Success = true, Message = "登录成功！", UserId = user.Id, Username = user.Username };
        }

        /// <summary>
        /// 注册新用户。
        /// </summary>
        /// <param name="username">要注册的新用户的用户名。</param>
        /// <param name="email">要注册的新用户的电子邮件地址。</param>
        /// <param name="fullName">新用户的全名。</param>
        /// <param name="password">新用户的明文密码。</param>
        /// <returns>
        /// 注册成功则返回新创建的 <see cref="User"/> 实体；
        /// 如果用户名或电子邮件已被占用，则返回 null。
        /// </returns>
        public async Task<User?> RegisterUserAsync(string username, string email, string fullName, string password)
        {
            // 记录注册尝试的日志信息，包含用户名和电子邮件。
            _logger.LogInformation("尝试注册新用户：Username='{Username}', Email='{Email}'", username, email);

            // 1. 检查用户名是否已存在。
            // 注意：GetUserByUsernameAsync 仅返回激活的用户。如果你希望即使是非激活用户也不能使用相同的用户名，
            // 你可能需要调整 GetUserByUsernameAsync 或在此处进行额外查询 (FirstOrDefaultAsync(u => u.Username == username))。
            if (await GetUserByUsernameAsync(username) != null)
            {
                _logger.LogWarning("注册失败：用户名 '{Username}' 已被占用。", username);
                return null; // 用户名已被占用，注册失败
            }

            // 2. 检查电子邮件是否已存在。
            // GetUserByEmailAsync 不检查用户是否激活，确保了电子邮件的全局唯一性。
            if (await GetUserByEmailAsync(email) != null)
            {
                _logger.LogWarning("注册失败：邮箱 '{Email}' 已被占用。", email);
                return null; // 电子邮件已被占用，注册失败
            }

            // 3. 为明文密码创建哈希值和随机盐值。
            var (hashedPassword, salt) = await CreatePasswordHash(password);

            // 4. 创建新的用户实体。
            var newUser = new User
            {
                Username = username,
                Email = email,
                FullName = fullName,
                PasswordHash = hashedPassword, // 存储经过哈希处理的密码
                Salt = salt,                 // 存储用于哈希的盐值
                IsActive = true,             // 默认情况下，新注册的用户是激活状态
                CreatedAt = DateTime.UtcNow  // 设置用户的创建时间为当前的 UTC 时间
            };

            // 5. 将新用户实体添加到数据库上下文。
            await _context.Users.AddAsync(newUser);
            // 6. 保存所有更改到数据库。
            await _context.SaveChangesAsync();

            // 记录新用户注册成功的日志，包含用户名和其分配的 ID。
            _logger.LogInformation("新用户 '{Username}' (ID: {UserId}) 注册成功。", newUser.Username, newUser.Id);

            return newUser; // 返回新创建的用户实体
        }
    }
}