using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace UniVerse.Api.Filters;

/// <summary>
/// Swagger operation filter that:
/// 1. Adds Bearer security requirement only to endpoints that actually require authentication.
/// 2. Appends a "Required roles: ..." remark to the operation description when role restrictions exist.
///
/// This replaces the global AddSecurityRequirement approach so anonymous endpoints
/// (auth/login, auth/refresh, auth/callback) don't show the lock icon in Swagger UI.
/// </summary>
public class AuthorizeOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        // Collect [Authorize] and [AllowAnonymous] from both the controller and the action.
        var actionAttributes     = context.MethodInfo.GetCustomAttributes(inherit: true);
        var controllerAttributes = context.MethodInfo.DeclaringType?
            .GetCustomAttributes(inherit: true) ?? [];

        var allAttributes = actionAttributes.Concat(controllerAttributes).ToList();

        var hasAllowAnonymous = allAttributes.OfType<AllowAnonymousAttribute>().Any();
        if (hasAllowAnonymous)
            return; // completely public — no lock icon

        var authorizeAttributes = allAttributes.OfType<AuthorizeAttribute>().ToList();
        if (authorizeAttributes.Count == 0)
            return; // no [Authorize] at all — also public

        // Collect all distinct roles across all [Authorize(Roles = "...")] attributes.
        var roles = authorizeAttributes
            .Where(a => !string.IsNullOrWhiteSpace(a.Roles))
            .SelectMany(a => a.Roles!.Split(',', StringSplitOptions.TrimEntries))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .OrderBy(r => r)
            .ToList();

        // Append role information to the operation description.
        var roleInfo = roles.Count > 0
            ? $"**Required roles:** {string.Join(", ", roles)}"
            : "**Required:** any authenticated user";

        operation.Description = string.IsNullOrWhiteSpace(operation.Description)
            ? roleInfo
            : $"{operation.Description}\n\n{roleInfo}";

        operation.Responses ??= new OpenApiResponses();

        // Add 401 / 403 responses if not already declared.
        if (!operation.Responses.ContainsKey("401"))
        {
            operation.Responses.Add("401", new OpenApiResponse
            {
                Description = "Unauthorized — JWT token missing or invalid"
            });
        }

        if (roles.Count > 0 && !operation.Responses.ContainsKey("403"))
        {
            operation.Responses.Add("403", new OpenApiResponse
            {
                Description = $"Forbidden — requires role: {string.Join(" or ", roles)}"
            });
        }

        // Add Bearer security requirement to this specific operation.
        // OpenAPI v2 (Microsoft.OpenApi 2.x) uses OpenApiSecuritySchemeReference
        // instead of OpenApiSecurityScheme with a Reference property.
        var bearerSchemeRef = new OpenApiSecuritySchemeReference("Bearer", context.Document);

        operation.Security ??= [];
        operation.Security.Add(new OpenApiSecurityRequirement
        {
            [bearerSchemeRef] = []
        });
    }
}
