using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using BookmarkApi.Core.Interfaces;
using BookmarkApi.Data.Entities;
using BookmarkApi.Shared.DTOs;
using BookmarkApi.Shared.DTOs.Auth;
using BookmarkApi.Shared.Enums;
using BookmarkApi.Shared.Models;

namespace BookmarkApi.Core.Services;

/// <summary>
/// 认证服务实现
/// </summary>
public class AuthService : IAuthService
{
    private readonly IFreeSql _freeSql;
    private readonly JwtSettings _jwtSettings;

    public AuthService(IFreeSql freeSql, IOptions<JwtSettings> jwtSettings)
    {
        _freeSql = freeSql;
        _jwtSettings = jwtSettings.Value;
    }

    /// <inheritdoc />
    public async Task<AuthResponse> RegisterAsync(RegisterRequest request)
    {
        // 检查邮箱是否已存在
        var existingUser = await _freeSql.Select<User>()
            .Where(u => u.Email == request.Email.ToLower())
            .FirstAsync();

        if (existingUser != null)
        {
            return AuthResponse.Fail("该邮箱已被注册");
        }

        // 创建用户
        var user = new User
        {
            Id = Guid.NewGuid(),
            Email = request.Email.ToLower(),
            UserName = request.UserName,
            PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
            Role = AdminRole.User,
            Status = UserStatus.Normal,
            CreatedAt = DateTime.UtcNow
        };

        await _freeSql.Insert(user).ExecuteAffrowsAsync();

        return AuthResponse.Fail("注册成功，请登录");
    }

    /// <inheritdoc />
    public async Task<AuthResponse> LoginAsync(LoginRequest request)
    {
        // 查找用户
        var user = await _freeSql.Select<User>()
            .Where(u => u.Email == request.Email.ToLower())
            .FirstAsync();

        if (user == null)
        {
            return AuthResponse.Fail("邮箱或密码错误");
        }

        // 检查用户状态
        if (user.Status == UserStatus.Disabled)
        {
            return AuthResponse.Fail("账号已被禁用");
        }

        // 验证密码
        if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
        {
            return AuthResponse.Fail("邮箱或密码错误");
        }

        // 查找或创建设备
        var device = await GetOrCreateDeviceAsync(user.Id, request);

        // 检查设备状态
        if (device.Status == DeviceStatus.Disabled)
        {
            return AuthResponse.Fail("该设备已被禁用");
        }

        // 生成令牌
        var (accessToken, expiresAt) = GenerateAccessToken(user, device);
        var refreshToken = await GenerateRefreshTokenAsync(user.Id, device.Id);

        // 更新用户最后登录时间
        await _freeSql.Update<User>()
            .Set(u => u.LastLoginAt, DateTime.UtcNow)
            .Where(u => u.Id == user.Id)
            .ExecuteAffrowsAsync();

        // 更新设备活跃时间
        await _freeSql.Update<Device>()
            .Set(d => d.LastActiveAt, DateTime.UtcNow)
            .Where(d => d.Id == device.Id)
            .ExecuteAffrowsAsync();

        return AuthResponse.Ok(
            accessToken,
            refreshToken,
            expiresAt,
            MapToUserDto(user),
            MapToDeviceDto(device)
        );
    }

    /// <inheritdoc />
    public async Task<bool> LogoutAsync(Guid userId, Guid deviceId)
    {
        // 撤销该设备的所有刷新令牌
        var affectedRows = await _freeSql.Update<RefreshToken>()
            .Set(t => t.IsRevoked, true)
            .Set(t => t.RevokedAt, DateTime.UtcNow)
            .Where(t => t.UserId == userId && t.DeviceId == deviceId && !t.IsRevoked)
            .ExecuteAffrowsAsync();

        return affectedRows > 0;
    }

    /// <inheritdoc />
    public async Task<AuthResponse> RefreshTokenAsync(string refreshToken)
    {
        // 查找刷新令牌
        var token = await _freeSql.Select<RefreshToken>()
            .Where(t => t.Token == refreshToken)
            .Include(t => t.User)
            .Include(t => t.Device)
            .FirstAsync();

        if (token == null)
        {
            return AuthResponse.Fail("无效的刷新令牌");
        }

        if (!token.IsActive)
        {
            return AuthResponse.Fail("刷新令牌已过期或已被撤销");
        }

        if (token.User == null || token.Device == null)
        {
            return AuthResponse.Fail("无效的刷新令牌");
        }

        // 检查用户状态
        if (token.User.Status == UserStatus.Disabled)
        {
            return AuthResponse.Fail("账号已被禁用");
        }

        // 检查设备状态
        if (token.Device.Status == DeviceStatus.Disabled)
        {
            return AuthResponse.Fail("该设备已被禁用");
        }

        // 撤销旧令牌
        token.IsRevoked = true;
        token.RevokedAt = DateTime.UtcNow;

        // 生成新令牌
        var (accessToken, expiresAt) = GenerateAccessToken(token.User, token.Device);
        var newRefreshToken = await GenerateRefreshTokenAsync(token.UserId, token.DeviceId, token.Id);

        // 更新旧令牌
        await _freeSql.Update<RefreshToken>()
            .SetSource(token)
            .ExecuteAffrowsAsync();

        // 更新设备活跃时间
        await _freeSql.Update<Device>()
            .Set(d => d.LastActiveAt, DateTime.UtcNow)
            .Where(d => d.Id == token.DeviceId)
            .ExecuteAffrowsAsync();

        return AuthResponse.Ok(
            accessToken,
            newRefreshToken,
            expiresAt,
            MapToUserDto(token.User),
            MapToDeviceDto(token.Device)
        );
    }

    /// <inheritdoc />
    public Task<bool> ValidateTokenAsync(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.UTF8.GetBytes(_jwtSettings.SecretKey);

        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = _jwtSettings.Issuer,
                ValidAudience = _jwtSettings.Audience,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ClockSkew = TimeSpan.Zero
            }, out _);

            return Task.FromResult(true);
        }
        catch
        {
            return Task.FromResult(false);
        }
    }

    /// <summary>
    /// 获取或创建设备
    /// </summary>
    private async Task<Device> GetOrCreateDeviceAsync(Guid userId, LoginRequest request)
    {
        Device? device = null;

        // 如果有设备指纹，尝试查找已存在的设备
        if (!string.IsNullOrEmpty(request.DeviceFingerprint))
        {
            device = await _freeSql.Select<Device>()
                .Where(d => d.UserId == userId && d.DeviceFingerprint == request.DeviceFingerprint)
                .FirstAsync();
        }

        if (device != null)
        {
            return device;
        }

        // 检查是否是用户的第一个设备（设为管理员设备）
        var hasDevices = await _freeSql.Select<Device>()
            .Where(d => d.UserId == userId)
            .AnyAsync();

        // 创建新设备
        device = new Device
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            DeviceName = request.DeviceName ?? "未命名设备",
            DeviceType = request.DeviceType ?? "未知",
            DeviceFingerprint = request.DeviceFingerprint,
            IsAdmin = !hasDevices, // 第一个设备自动成为管理员设备
            Status = DeviceStatus.Normal,
            CreatedAt = DateTime.UtcNow,
            LastActiveAt = DateTime.UtcNow
        };

        await _freeSql.Insert(device).ExecuteAffrowsAsync();

        return device;
    }

    /// <summary>
    /// 生成访问令牌
    /// </summary>
    private (string token, DateTime expiresAt) GenerateAccessToken(User user, Device device)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var expiresAt = DateTime.UtcNow.AddMinutes(_jwtSettings.AccessTokenExpiryMinutes);

        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Email, user.Email),
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim(ClaimTypes.Role, user.Role.ToString()),
            new Claim("device_id", device.Id.ToString()),
            new Claim("is_admin_device", device.IsAdmin.ToString().ToLower())
        };

        var token = new JwtSecurityToken(
            issuer: _jwtSettings.Issuer,
            audience: _jwtSettings.Audience,
            claims: claims,
            expires: expiresAt,
            signingCredentials: credentials
        );

        return (new JwtSecurityTokenHandler().WriteToken(token), expiresAt);
    }

    /// <summary>
    /// 生成刷新令牌
    /// </summary>
    private async Task<string> GenerateRefreshTokenAsync(Guid userId, Guid deviceId, Guid? replacedTokenId = null)
    {
        var tokenValue = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64));

        var refreshToken = new RefreshToken
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            DeviceId = deviceId,
            Token = tokenValue,
            ExpiresAt = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays),
            CreatedAt = DateTime.UtcNow,
            IsRevoked = false,
            ReplacedByTokenId = null
        };

        await _freeSql.Insert(refreshToken).ExecuteAffrowsAsync();

        // 更新被替换的令牌
        if (replacedTokenId.HasValue)
        {
            await _freeSql.Update<RefreshToken>()
                .Set(t => t.ReplacedByTokenId, refreshToken.Id)
                .Where(t => t.Id == replacedTokenId.Value)
                .ExecuteAffrowsAsync();
        }

        return tokenValue;
    }

    private static UserDto MapToUserDto(User user)
    {
        return new UserDto
        {
            Id = user.Id,
            Email = user.Email,
            UserName = user.UserName,
            Avatar = user.Avatar,
            Role = user.Role,
            CreatedAt = user.CreatedAt,
            LastLoginAt = user.LastLoginAt
        };
    }

    private static DeviceDto MapToDeviceDto(Device device)
    {
        return new DeviceDto
        {
            Id = device.Id,
            DeviceName = device.DeviceName,
            DeviceType = device.DeviceType,
            IsAdmin = device.IsAdmin,
            CreatedAt = device.CreatedAt,
            LastActiveAt = device.LastActiveAt,
            Status = device.Status
        };
    }
}

