using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

namespace SatelliteProvider.Api.Authentication;

public static class AuthenticationServiceCollectionExtensions
{
    public const string JwtSecretEnvVar = "JWT_SECRET";
    public const string JwtSecretConfigKey = "Jwt:Secret";
    public const string JwtIssuerEnvVar = "JWT_ISSUER";
    public const string JwtIssuerConfigKey = "Jwt:Issuer";
    public const string JwtAudienceEnvVar = "JWT_AUDIENCE";
    public const string JwtAudienceConfigKey = "Jwt:Audience";
    public const int MinSecretByteLength = 32;

    public static IServiceCollection AddSatelliteJwt(this IServiceCollection services, IConfiguration configuration)
    {
        ArgumentNullException.ThrowIfNull(services);
        ArgumentNullException.ThrowIfNull(configuration);

        var secret = ResolveSecretOrThrow(configuration);
        var issuer = ResolveRequiredOrThrow(configuration, JwtIssuerEnvVar, JwtIssuerConfigKey, "JWT issuer");
        var audience = ResolveRequiredOrThrow(configuration, JwtAudienceEnvVar, JwtAudienceConfigKey, "JWT audience");
        var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));

        services
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = signingKey,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.FromSeconds(30),
                    ValidateIssuer = true,
                    ValidIssuer = issuer,
                    ValidateAudience = true,
                    ValidAudience = audience,
                    RequireSignedTokens = true,
                    RequireExpirationTime = true
                };
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
                        if (!string.IsNullOrEmpty(authHeader)
                            && authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                        {
                            context.Token = authHeader["Bearer ".Length..].Trim();
                        }

                        return Task.CompletedTask;
                    },
                };
            });

        return services;
    }

    internal static string ResolveSecretOrThrow(IConfiguration configuration)
    {
        var secret = Environment.GetEnvironmentVariable(JwtSecretEnvVar);
        if (string.IsNullOrWhiteSpace(secret))
        {
            secret = configuration[JwtSecretConfigKey];
        }

        if (string.IsNullOrWhiteSpace(secret))
        {
            throw new InvalidOperationException(
                $"JWT secret is not configured. Set the {JwtSecretEnvVar} environment variable " +
                $"or the {JwtSecretConfigKey} configuration key to a value of at least {MinSecretByteLength} bytes.");
        }

        var byteLength = Encoding.UTF8.GetByteCount(secret);
        if (byteLength < MinSecretByteLength)
        {
            throw new InvalidOperationException(
                $"JWT secret is too short ({byteLength} bytes). HMAC-SHA256 requires at least {MinSecretByteLength} bytes " +
                $"per RFC 2104 §3. Set {JwtSecretEnvVar} or {JwtSecretConfigKey} to a longer value.");
        }

        return secret;
    }

    // AZ-494: required non-secret config (iss / aud). Fail-fast contract mirrors
    // JWT_SECRET — missing or whitespace-only values throw at startup so a
    // production deploy without the operator-confirmed values cannot silently
    // accept tokens with arbitrary issuer/audience claims.
    internal static string ResolveRequiredOrThrow(IConfiguration configuration, string envVar, string configKey, string humanLabel)
    {
        var value = Environment.GetEnvironmentVariable(envVar);
        if (string.IsNullOrWhiteSpace(value))
        {
            value = configuration[configKey];
        }

        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException(
                $"{humanLabel} is not configured. Set the {envVar} environment variable " +
                $"or the {configKey} configuration key. (See AZ-494 task spec.)");
        }

        return value;
    }
}
