using System;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using StellaOps.Auth.Abstractions;

namespace StellaOps.Auth.ServerIntegration;

/// <summary>
/// Dependency injection helpers for configuring StellaOps resource server authentication.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Registers JWT bearer authentication and related authorisation helpers using the provided configuration section.
    /// </summary>
    /// <param name="services">The service collection.</param>
    /// <param name="configuration">Application configuration.</param>
    /// <param name="configurationSection">
    /// Optional configuration section path. Defaults to <c>Authority:ResourceServer</c>. Provide <c>null</c> to skip binding.
    /// </param>
    /// <param name="configure">Optional callback allowing additional mutation of <see cref="StellaOpsResourceServerOptions"/>.</param>
    public static IServiceCollection AddStellaOpsResourceServerAuthentication(
        this IServiceCollection services,
        IConfiguration configuration,
        string? configurationSection = "Authority:ResourceServer",
        Action<StellaOpsResourceServerOptions>? configure = null)
    {
        ArgumentNullException.ThrowIfNull(services);
        ArgumentNullException.ThrowIfNull(configuration);

        services.AddHttpContextAccessor();
        services.AddAuthorization();
        services.AddStellaOpsScopeHandler();
        services.TryAddSingleton<StellaOpsBypassEvaluator>();

        var optionsBuilder = services.AddOptions<StellaOpsResourceServerOptions>();
        if (!string.IsNullOrWhiteSpace(configurationSection))
        {
            optionsBuilder.Bind(configuration.GetSection(configurationSection));
        }

        if (configure is not null)
        {
            optionsBuilder.Configure(configure);
        }

        optionsBuilder.PostConfigure(static options => options.Validate());

        var authenticationBuilder = services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme ??= StellaOpsAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme ??= StellaOpsAuthenticationDefaults.AuthenticationScheme;
        });

        authenticationBuilder.AddJwtBearer(StellaOpsAuthenticationDefaults.AuthenticationScheme);

        services.AddOptions<JwtBearerOptions>(StellaOpsAuthenticationDefaults.AuthenticationScheme)
            .Configure<IOptionsMonitor<StellaOpsResourceServerOptions>>((jwt, monitor) =>
            {
                var resourceOptions = monitor.CurrentValue;

                jwt.Authority = resourceOptions.AuthorityUri.ToString();
                if (!string.IsNullOrWhiteSpace(resourceOptions.MetadataAddress))
                {
                    jwt.MetadataAddress = resourceOptions.MetadataAddress;
                }
                jwt.RequireHttpsMetadata = resourceOptions.RequireHttpsMetadata;
                jwt.BackchannelTimeout = resourceOptions.BackchannelTimeout;
                jwt.MapInboundClaims = false;
                jwt.SaveToken = false;

                jwt.TokenValidationParameters ??= new TokenValidationParameters();
                jwt.TokenValidationParameters.ValidIssuer = resourceOptions.AuthorityUri.ToString();
                jwt.TokenValidationParameters.ValidateAudience = resourceOptions.Audiences.Count > 0;
                jwt.TokenValidationParameters.ValidAudiences = resourceOptions.Audiences;
                jwt.TokenValidationParameters.ClockSkew = resourceOptions.TokenClockSkew;
                jwt.TokenValidationParameters.NameClaimType = ClaimTypes.Name;
                jwt.TokenValidationParameters.RoleClaimType = ClaimTypes.Role;
            });

        return services;
    }
}
