using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using FluentAssertions;
using Microsoft.IdentityModel.Tokens;
using SatelliteProvider.TestSupport;

namespace SatelliteProvider.Tests.Authentication;

public class JwtTokenFactoryTests
{
    private const string Secret = "factory-secret-that-is-longer-than-thirty-two-bytes-bytes";

    [Fact]
    public void Create_ProducesTokenValidatedByMatchingParameters()
    {
        // Arrange — disable inbound claim remapping so the test asserts
        // the factory's actual output ("sub", "email", ...) rather than
        // the framework's ClaimTypes.* aliases.
        var token = JwtTokenFactory.Create(Secret, subject: "alice");
        var parameters = BuildParameters(Secret);
        var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };

        // Act
        var principal = handler.ValidateToken(token, parameters, out var validatedToken);

        // Assert
        principal.Identity!.IsAuthenticated.Should().BeTrue();
        principal.FindFirst(JwtRegisteredClaimNames.Sub)!.Value.Should().Be("alice");
        validatedToken.Should().BeOfType<JwtSecurityToken>();
    }

    [Fact]
    public void Create_WithExtraClaims_PropagatesClaimsThroughValidation()
    {
        // Arrange
        var claims = new[]
        {
            new Claim("email", "alice@example.com"),
            new Claim("role", "operator"),
            new Claim("permissions", "GPS"),
            new Claim("permissions", "FL")
        };
        var token = JwtTokenFactory.Create(Secret, extraClaims: claims);
        var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };

        // Act
        var principal = handler.ValidateToken(token, BuildParameters(Secret), out _);

        // Assert
        principal.FindAll("permissions").Select(c => c.Value).Should().BeEquivalentTo(new[] { "GPS", "FL" });
        principal.FindFirst("email")!.Value.Should().Be("alice@example.com");
    }

    [Fact]
    public void CreateExpired_TokenFailsValidationWithLifetimeException()
    {
        // Arrange
        var token = JwtTokenFactory.CreateExpired(Secret);
        var handler = new JwtSecurityTokenHandler();

        // Act
        var act = () => handler.ValidateToken(token, BuildParameters(Secret), out _);

        // Assert
        act.Should().Throw<SecurityTokenExpiredException>();
    }

    [Fact]
    public void TamperSignature_TokenFailsValidationWithSignatureException()
    {
        // Arrange
        var token = JwtTokenFactory.Create(Secret);
        var tampered = JwtTokenFactory.TamperSignature(token);
        var handler = new JwtSecurityTokenHandler();

        // Act
        var act = () => handler.ValidateToken(tampered, BuildParameters(Secret), out _);

        // Assert
        act.Should().Throw<SecurityTokenInvalidSignatureException>();
    }

    private static TokenValidationParameters BuildParameters(string secret) => new()
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)),
        ValidateLifetime = true,
        ClockSkew = TimeSpan.FromSeconds(30),
        ValidateIssuer = false,
        ValidateAudience = false,
        RequireSignedTokens = true,
        RequireExpirationTime = true
    };
}
