using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Core.Services;
using HoneyBox.Model.Entities;
using HoneyBox.Model.Models.Auth;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace HoneyBox.Tests.Services;

public class JwtServiceTests
{
    private readonly JwtSettings _jwtSettings;
    private readonly Mock<ILogger<JwtService>> _mockLogger;
    private readonly JwtService _jwtService;

    public JwtServiceTests()
    {
        _jwtSettings = new JwtSettings
        {
            Secret = "your-secret-key-must-be-at-least-32-characters-long-for-hs256",
            Issuer = "HoneyBox",
            Audience = "HoneyBoxUsers",
            ExpirationMinutes = 1440,
            RefreshTokenExpirationDays = 7
        };

        _mockLogger = new Mock<ILogger<JwtService>>();
        _jwtService = new JwtService(_jwtSettings, _mockLogger.Object);
    }

    [Fact]
    public void GenerateToken_WithValidUser_ReturnsValidToken()
    {
        // Arrange
        var user = new User
        {
            Id = 1,
            Nickname = "TestUser",
            Uid = "uid123",
            OpenId = "openid123",
            HeadImg = "https://example.com/avatar.jpg",
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };

        // Act
        var token = _jwtService.GenerateToken(user);

        // Assert
        Assert.NotNull(token);
        Assert.NotEmpty(token);
        
        // Verify token can be read
        var handler = new JwtSecurityTokenHandler();
        var jwtToken = handler.ReadJwtToken(token);
        Assert.NotNull(jwtToken);
        Assert.Equal(_jwtSettings.Issuer, jwtToken.Issuer);
        Assert.Equal(_jwtSettings.Audience, jwtToken.Audiences.First());
    }

    [Fact]
    public void GenerateToken_WithNullUser_ThrowsArgumentNullException()
    {
        // Act & Assert
        Assert.Throws<ArgumentNullException>(() => _jwtService.GenerateToken(null!));
    }

    [Fact]
    public void ValidateToken_WithValidToken_ReturnsClaimsPrincipal()
    {
        // Arrange
        var user = new User
        {
            Id = 1,
            Nickname = "TestUser",
            Uid = "uid123",
            OpenId = "openid123",
            HeadImg = "https://example.com/avatar.jpg",
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };
        var token = _jwtService.GenerateToken(user);

        // Act
        var principal = _jwtService.ValidateToken(token);

        // Assert
        Assert.NotNull(principal);
        var userIdClaim = principal.FindFirst(ClaimTypes.NameIdentifier);
        Assert.NotNull(userIdClaim);
        Assert.Equal("1", userIdClaim.Value);
    }

    [Fact]
    public void ValidateToken_WithInvalidToken_ReturnsNull()
    {
        // Act
        var principal = _jwtService.ValidateToken("invalid.token.here");

        // Assert
        Assert.Null(principal);
    }

    [Fact]
    public void ValidateToken_WithNullToken_ReturnsNull()
    {
        // Act
        var principal = _jwtService.ValidateToken(null!);

        // Assert
        Assert.Null(principal);
    }

    [Fact]
    public void ValidateToken_WithEmptyToken_ReturnsNull()
    {
        // Act
        var principal = _jwtService.ValidateToken(string.Empty);

        // Assert
        Assert.Null(principal);
    }

    [Fact]
    public void GetUserIdFromToken_WithValidToken_ReturnsCorrectUserId()
    {
        // Arrange
        var user = new User
        {
            Id = 42,
            Nickname = "TestUser",
            Uid = "uid123",
            OpenId = "openid123",
            HeadImg = "https://example.com/avatar.jpg",
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };
        var token = _jwtService.GenerateToken(user);

        // Act
        var userId = _jwtService.GetUserIdFromToken(token);

        // Assert
        Assert.NotNull(userId);
        Assert.Equal(42, userId);
    }

    [Fact]
    public void GetUserIdFromToken_WithInvalidToken_ReturnsNull()
    {
        // Act
        var userId = _jwtService.GetUserIdFromToken("invalid.token.here");

        // Assert
        Assert.Null(userId);
    }

    [Fact]
    public void GetUserIdFromToken_WithNullToken_ReturnsNull()
    {
        // Act
        var userId = _jwtService.GetUserIdFromToken(null!);

        // Assert
        Assert.Null(userId);
    }

    [Fact]
    public void GetUserIdFromToken_WithEmptyToken_ReturnsNull()
    {
        // Act
        var userId = _jwtService.GetUserIdFromToken(string.Empty);

        // Assert
        Assert.Null(userId);
    }

    /// <summary>
    /// Property 3: 登录成功Token生成
    /// For any valid user, generating a token should produce a valid JWT that contains the user's ID
    /// and can be validated to extract the same user ID.
    /// Validates: Requirements 3.1, 3.2
    /// </summary>
    [Property(MaxTest = 100)]
    public bool GeneratedTokenContainsCorrectUserId(PositiveInt userId, NonEmptyString nickname)
    {
        var user = new User
        {
            Id = userId.Item,
            Nickname = nickname.Item,
            Uid = $"uid{userId.Item}",
            OpenId = $"openid{userId.Item}",
            HeadImg = "https://example.com/avatar.jpg",
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };

        var token = _jwtService.GenerateToken(user);
        var extractedUserId = _jwtService.GetUserIdFromToken(token);

        return extractedUserId == userId.Item;
    }
}
