using System.IdentityModel.Tokens.Jwt;
using FluentAssertions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using SatelliteProvider.Api.Authentication;
using SatelliteProvider.TestSupport;
using AuthExtensions = SatelliteProvider.Api.Authentication.AuthenticationServiceCollectionExtensions;

namespace SatelliteProvider.Tests.Authentication;

public class AuthenticationServiceCollectionExtensionsTests : IDisposable
{
    private const string ValidSecret = "test-secret-that-is-definitely-longer-than-32-bytes";
    private readonly string? _originalEnv;

    public AuthenticationServiceCollectionExtensionsTests()
    {
        _originalEnv = Environment.GetEnvironmentVariable(AuthExtensions.JwtSecretEnvVar);
        Environment.SetEnvironmentVariable(AuthExtensions.JwtSecretEnvVar, null);
    }

    public void Dispose()
    {
        Environment.SetEnvironmentVariable(AuthExtensions.JwtSecretEnvVar, _originalEnv);
        GC.SuppressFinalize(this);
    }

    [Fact]
    public void AddSatelliteJwt_RegistersJwtBearerScheme()
    {
        // Arrange
        var services = new ServiceCollection();
        services.AddLogging();
        var configuration = BuildConfiguration(("Jwt:Secret", ValidSecret));

        // Act
        services.AddSatelliteJwt(configuration);
        var provider = services.BuildServiceProvider();
        var schemeProvider = provider.GetRequiredService<IAuthenticationSchemeProvider>();
        var scheme = schemeProvider.GetSchemeAsync(JwtBearerDefaults.AuthenticationScheme).GetAwaiter().GetResult();

        // Assert
        scheme.Should().NotBeNull("JwtBearer scheme should be registered");
        scheme!.HandlerType.Should().Be(typeof(JwtBearerHandler));
    }

    [Fact]
    public void AddSatelliteJwt_ConfiguresTokenValidationParameters_AsPerContract()
    {
        // Arrange
        var services = new ServiceCollection();
        services.AddLogging();
        var configuration = BuildConfiguration(("Jwt:Secret", ValidSecret));

        // Act
        services.AddSatelliteJwt(configuration);
        var provider = services.BuildServiceProvider();
        var options = provider.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().Get(JwtBearerDefaults.AuthenticationScheme);

        // Assert
        var p = options.TokenValidationParameters;
        p.ValidateIssuerSigningKey.Should().BeTrue();
        p.ValidateLifetime.Should().BeTrue();
        p.ValidateIssuer.Should().BeFalse();
        p.ValidateAudience.Should().BeFalse();
        p.RequireSignedTokens.Should().BeTrue();
        p.RequireExpirationTime.Should().BeTrue();
        p.ClockSkew.Should().Be(TimeSpan.FromSeconds(30));
        p.IssuerSigningKey.Should().BeOfType<SymmetricSecurityKey>();
    }

    [Fact]
    public void AddSatelliteJwt_ThrowsOnMissingSecret()
    {
        // Arrange
        var services = new ServiceCollection();
        var configuration = BuildConfiguration();

        // Act
        var act = () => services.AddSatelliteJwt(configuration);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*JWT secret is not configured*");
    }

    [Fact]
    public void AddSatelliteJwt_ThrowsOnEmptySecret()
    {
        // Arrange
        var services = new ServiceCollection();
        var configuration = BuildConfiguration(("Jwt:Secret", ""));

        // Act
        var act = () => services.AddSatelliteJwt(configuration);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*JWT secret is not configured*");
    }

    [Fact]
    public void AddSatelliteJwt_ThrowsOnShortSecret()
    {
        // Arrange
        var services = new ServiceCollection();
        var configuration = BuildConfiguration(("Jwt:Secret", "too-short-secret"));

        // Act
        var act = () => services.AddSatelliteJwt(configuration);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*at least 32 bytes*");
    }

    [Fact]
    public void AddSatelliteJwt_PrefersEnvironmentVariableOverConfiguration()
    {
        // Arrange
        const string envSecret = "env-secret-also-longer-than-thirty-two-bytes-for-hmac";
        Environment.SetEnvironmentVariable(AuthExtensions.JwtSecretEnvVar, envSecret);
        var services = new ServiceCollection();
        services.AddLogging();
        var configuration = BuildConfiguration(("Jwt:Secret", "config-secret-also-32-bytes-long-aaaaaaaaaa"));

        // Act
        services.AddSatelliteJwt(configuration);
        var provider = services.BuildServiceProvider();
        var options = provider.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().Get(JwtBearerDefaults.AuthenticationScheme);
        var token = JwtTokenFactory.Create(envSecret);
        var handler = new JwtSecurityTokenHandler();
        var act = () => handler.ValidateToken(token, options.TokenValidationParameters, out _);

        // Assert
        act.Should().NotThrow("token signed with env secret must validate when env secret takes precedence");
    }

    private static IConfiguration BuildConfiguration(params (string Key, string Value)[] pairs)
    {
        var builder = new ConfigurationBuilder();
        if (pairs.Length > 0)
        {
            builder.AddInMemoryCollection(pairs.Select(p => new KeyValuePair<string, string?>(p.Key, p.Value)));
        }
        return builder.Build();
    }
}
