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 const string ValidIssuer = "https://test-issuer.example/";
    private const string ValidAudience = "satellite-provider-tests";
    private readonly string? _originalSecret;
    private readonly string? _originalIssuer;
    private readonly string? _originalAudience;

    public AuthenticationServiceCollectionExtensionsTests()
    {
        _originalSecret = Environment.GetEnvironmentVariable(AuthExtensions.JwtSecretEnvVar);
        _originalIssuer = Environment.GetEnvironmentVariable(AuthExtensions.JwtIssuerEnvVar);
        _originalAudience = Environment.GetEnvironmentVariable(AuthExtensions.JwtAudienceEnvVar);
        Environment.SetEnvironmentVariable(AuthExtensions.JwtSecretEnvVar, null);
        Environment.SetEnvironmentVariable(AuthExtensions.JwtIssuerEnvVar, null);
        Environment.SetEnvironmentVariable(AuthExtensions.JwtAudienceEnvVar, null);
    }

    public void Dispose()
    {
        Environment.SetEnvironmentVariable(AuthExtensions.JwtSecretEnvVar, _originalSecret);
        Environment.SetEnvironmentVariable(AuthExtensions.JwtIssuerEnvVar, _originalIssuer);
        Environment.SetEnvironmentVariable(AuthExtensions.JwtAudienceEnvVar, _originalAudience);
        GC.SuppressFinalize(this);
    }

    [Fact]
    public void AddSatelliteJwt_RegistersJwtBearerScheme()
    {
        // Arrange
        var services = new ServiceCollection();
        services.AddLogging();
        var configuration = BuildValidConfiguration();

        // 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 = BuildValidConfiguration();

        // 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().BeTrue("AZ-494 flipped issuer validation on");
        p.ValidIssuer.Should().Be(ValidIssuer);
        p.ValidateAudience.Should().BeTrue("AZ-494 flipped audience validation on");
        p.ValidAudience.Should().Be(ValidAudience);
        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(
            ("Jwt:Issuer", ValidIssuer),
            ("Jwt:Audience", ValidAudience));

        // 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", ""),
            ("Jwt:Issuer", ValidIssuer),
            ("Jwt:Audience", ValidAudience));

        // 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"),
            ("Jwt:Issuer", ValidIssuer),
            ("Jwt:Audience", ValidAudience));

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

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

    [Fact]
    public void AddSatelliteJwt_ThrowsOnMissingIssuer()
    {
        // Arrange — AZ-494 AC-4: fail-fast on missing issuer.
        var services = new ServiceCollection();
        var configuration = BuildConfiguration(
            ("Jwt:Secret", ValidSecret),
            ("Jwt:Audience", ValidAudience));

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

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*JWT issuer is not configured*")
            .Where(ex => ex.Message.Contains(AuthExtensions.JwtIssuerEnvVar));
    }

    [Fact]
    public void AddSatelliteJwt_ThrowsOnEmptyIssuer()
    {
        // Arrange
        var services = new ServiceCollection();
        var configuration = BuildConfiguration(
            ("Jwt:Secret", ValidSecret),
            ("Jwt:Issuer", "   "),
            ("Jwt:Audience", ValidAudience));

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

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

    [Fact]
    public void AddSatelliteJwt_ThrowsOnMissingAudience()
    {
        // Arrange — AZ-494 AC-4: fail-fast on missing audience.
        var services = new ServiceCollection();
        var configuration = BuildConfiguration(
            ("Jwt:Secret", ValidSecret),
            ("Jwt:Issuer", ValidIssuer));

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

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*JWT audience is not configured*")
            .Where(ex => ex.Message.Contains(AuthExtensions.JwtAudienceEnvVar));
    }

    [Fact]
    public void AddSatelliteJwt_ThrowsOnEmptyAudience()
    {
        // Arrange
        var services = new ServiceCollection();
        var configuration = BuildConfiguration(
            ("Jwt:Secret", ValidSecret),
            ("Jwt:Issuer", ValidIssuer),
            ("Jwt:Audience", ""));

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

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

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

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

        // Assert
        act.Should().NotThrow("token signed with env secret + minted with env iss/aud must validate when env precedence applies");
        options.TokenValidationParameters.ValidIssuer.Should().Be(envIssuer);
        options.TokenValidationParameters.ValidAudience.Should().Be(envAudience);
    }

    private static IConfiguration BuildValidConfiguration() => BuildConfiguration(
        ("Jwt:Secret", ValidSecret),
        ("Jwt:Issuer", ValidIssuer),
        ("Jwt:Audience", ValidAudience));

    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();
    }
}
