using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Routing;
using Tiku.Application.Security;

namespace Tiku.Api.Security;

public sealed record EndpointAuthorizationMetadata(
    string Realm,
    string? Module,
    IReadOnlyList<string> RequiredFeatures,
    string? Permission,
    CapabilityOperation Operation,
    bool RequiresAllDataScope,
    string AuditAction);

internal sealed class EndpointAuthorizationMetadataConvention : IApplicationModelConvention
{
    public void Apply(ApplicationModel application)
    {
        foreach (var controller in application.Controllers)
        {
            foreach (var action in controller.Actions)
            {
                var anonymous = controller.Attributes.OfType<AllowAnonymousAttribute>().Any() ||
                                action.Attributes.OfType<AllowAnonymousAttribute>().Any();
                if (anonymous)
                {
                    continue;
                }

                var policies = controller.Attributes.OfType<AuthorizeAttribute>()
                    .Concat(action.Attributes.OfType<AuthorizeAttribute>())
                    .Select(attribute => attribute.Policy)
                    .Where(policy => !string.IsNullOrWhiteSpace(policy))
                    .Cast<string>()
                    .ToArray();
                var permission = policies.FirstOrDefault(policy =>
                    BackendPermissions.Tenant.Contains(policy) || BackendPermissions.Platform.Contains(policy));
                var realm = permission is not null && BackendPermissions.Platform.Contains(permission) ||
                            policies.Any(policy => policy.StartsWith("platform", StringComparison.Ordinal))
                    ? "platform"
                    : permission is not null && BackendPermissions.Tenant.Contains(permission) ||
                      policies.Any(policy => policy.StartsWith("tenant", StringComparison.Ordinal))
                        ? "tenant"
                        : "authenticated";
                var module = permission is null ? null : PermissionModuleCatalog.ResolvePermissionModuleCode(permission);
                var requiredFeatures = controller.Attributes.OfType<RequireSaasFeatureAttribute>()
                    .Concat(action.Attributes.OfType<RequireSaasFeatureAttribute>())
                    .Select(attribute => attribute.FeatureCode)
                    .Concat(module is not null &&
                            PermissionModuleCatalog.RequiredFeatures.TryGetValue(module, out var moduleFeature) &&
                            moduleFeature is not null
                        ? [moduleFeature]
                        : [])
                    .Distinct(StringComparer.Ordinal)
                    .Order(StringComparer.Ordinal)
                    .Concat(action.Attributes.OfType<RequireSaasFeatureFromRouteAttribute>()
                        .Select(attribute => $"route:{attribute.RouteValueName}"))
                    .ToArray();
                var httpMethods = action.Attributes.OfType<HttpMethodAttribute>()
                    .SelectMany(attribute => attribute.HttpMethods)
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .ToArray();
                var operation = httpMethods.All(IsSafeMethod)
                    ? CapabilityOperation.Read
                    : CapabilityOperation.Write;
                var route = $"{controller.ControllerName}.{action.ActionName}";
                var metadata = new EndpointAuthorizationMetadata(
                    realm,
                    module,
                    requiredFeatures,
                    permission,
                    operation,
                    policies.Contains(TikuPolicies.TenantContentManageAllScope, StringComparer.Ordinal) ||
                    policies.Contains(TikuPolicies.TenantAllDataScope, StringComparer.Ordinal) ||
                    policies.Contains(TikuPolicies.TenantCommerceOperateAllScope, StringComparer.Ordinal),
                    $"{string.Join(',', httpMethods.Order(StringComparer.Ordinal))}:{route}");
                foreach (var selector in action.Selectors)
                {
                    selector.EndpointMetadata.Add(metadata);
                }
            }
        }
    }

    private static bool IsSafeMethod(string method) =>
        HttpMethods.IsGet(method) || HttpMethods.IsHead(method) || HttpMethods.IsOptions(method);
}
