using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using StellaOps.Auth.Abstractions;

namespace StellaOps.Auth.ServerIntegration;

/// <summary>
/// Handles <see cref="StellaOpsScopeRequirement"/> evaluation.
/// </summary>
internal sealed class StellaOpsScopeAuthorizationHandler : AuthorizationHandler<StellaOpsScopeRequirement>
{
    private readonly IHttpContextAccessor httpContextAccessor;
    private readonly StellaOpsBypassEvaluator bypassEvaluator;
    private readonly ILogger<StellaOpsScopeAuthorizationHandler> logger;

    public StellaOpsScopeAuthorizationHandler(
        IHttpContextAccessor httpContextAccessor,
        StellaOpsBypassEvaluator bypassEvaluator,
        ILogger<StellaOpsScopeAuthorizationHandler> logger)
    {
        this.httpContextAccessor = httpContextAccessor;
        this.bypassEvaluator = bypassEvaluator;
        this.logger = logger;
    }

    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        StellaOpsScopeRequirement requirement)
    {
        HashSet<string>? userScopes = null;

        if (context.User?.Identity?.IsAuthenticated == true)
        {
            userScopes = ExtractScopes(context.User);

            foreach (var scope in requirement.RequiredScopes)
            {
                if (userScopes.Contains(scope))
                {
                    context.Succeed(requirement);
                    return Task.CompletedTask;
                }
            }
        }

        var httpContext = httpContextAccessor.HttpContext;

        if (httpContext is not null && bypassEvaluator.ShouldBypass(httpContext, requirement.RequiredScopes))
        {
            context.Succeed(requirement);
            return Task.CompletedTask;
        }

        if (logger.IsEnabled(LogLevel.Debug))
        {
            var required = string.Join(", ", requirement.RequiredScopes);
            var principalScopes = userScopes is null || userScopes.Count == 0
                ? "(none)"
                : string.Join(", ", userScopes);

            logger.LogDebug(
                "Scope requirement not satisfied. Required={RequiredScopes}; PrincipalScopes={PrincipalScopes}; Remote={Remote}",
                required,
                principalScopes,
                httpContext?.Connection.RemoteIpAddress);
        }

        return Task.CompletedTask;
    }

    private static HashSet<string> ExtractScopes(ClaimsPrincipal principal)
    {
        var scopes = new HashSet<string>(StringComparer.Ordinal);

        foreach (var claim in principal.FindAll(StellaOpsClaimTypes.ScopeItem))
        {
            if (string.IsNullOrWhiteSpace(claim.Value))
            {
                continue;
            }

            scopes.Add(claim.Value);
        }

        foreach (var claim in principal.FindAll(StellaOpsClaimTypes.Scope))
        {
            if (string.IsNullOrWhiteSpace(claim.Value))
            {
                continue;
            }

            var parts = claim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

            foreach (var part in parts)
            {
                var normalized = StellaOpsScopes.Normalize(part);
                if (normalized is not null)
                {
                    scopes.Add(normalized);
                }
            }
        }

        return scopes;
    }
}
