using Microsoft.AspNetCore.Mvc;
using Tiku.Application.Security;

namespace Tiku.Api.Security;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public sealed class RequireSaasFeatureAttribute(string featureCode) : Attribute
{
    public string FeatureCode { get; } = featureCode;
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class RequireSaasFeatureFromRouteAttribute(string routeValueName) : Attribute
{
    public string RouteValueName { get; } = routeValueName;
}

public sealed class SaasFeatureAccessMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(
        HttpContext context,
        ITenantContext tenantContext,
        IFeatureAccessService featureAccessService)
    {
        var requirements = context.GetEndpoint()?.Metadata.GetOrderedMetadata<RequireSaasFeatureAttribute>();
        var routeRequirements =
            context.GetEndpoint()?.Metadata.GetOrderedMetadata<RequireSaasFeatureFromRouteAttribute>();
        if ((requirements is null || requirements.Count == 0) &&
            (routeRequirements is null || routeRequirements.Count == 0))
        {
            await next(context);
            return;
        }

        if (!tenantContext.IsResolved || tenantContext.IsSystem || tenantContext.TenantId is not { } tenantId)
        {
            // Some platform-hosted public entry points resolve their tenant from a
            // signed code inside the application service. They must keep their
            // existing 404 semantics when no request tenant has been established.
            await next(context);
            return;
        }

        var operation = HttpMethods.IsGet(context.Request.Method) ||
                        HttpMethods.IsHead(context.Request.Method) ||
                        HttpMethods.IsOptions(context.Request.Method)
            ? FeatureAccessOperation.Read
            : FeatureAccessOperation.Write;
        foreach (var requirement in (requirements ?? []).DistinctBy(value => value.FeatureCode, StringComparer.Ordinal))
        {
            var decision = await featureAccessService.EvaluateAsync(
                tenantId,
                requirement.FeatureCode,
                operation,
                context.RequestAborted);
            if (!decision.Allowed)
            {
                await WriteForbiddenAsync(
                    context,
                    decision.DenialCode ?? "feature_not_available",
                    $"The SaaS feature '{requirement.FeatureCode}' is not available.");
                return;
            }
        }


        foreach (var requirement in routeRequirements ?? [])
        {
            var routeValue = context.Request.RouteValues[requirement.RouteValueName]?.ToString();
            var featureCode = ResolveRouteFeature(routeValue);
            if (featureCode is null)
            {
                await WriteForbiddenAsync(context, "feature_route_value_invalid",
                    "The requested content module is not available.");
                return;
            }

            var decision = await featureAccessService.EvaluateAsync(
                tenantId,
                featureCode,
                operation,
                context.RequestAborted);
            if (!decision.Allowed)
            {
                await WriteForbiddenAsync(
                    context,
                    decision.DenialCode ?? "feature_not_available",
                    $"The SaaS feature '{featureCode}' is not available.");
                return;
            }
        }

        await next(context);
    }

    private static Task WriteForbiddenAsync(HttpContext context, string code, string title)
    {
        context.Response.StatusCode = StatusCodes.Status403Forbidden;
        return context.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = StatusCodes.Status403Forbidden,
            Title = title,
            Instance = context.Request.Path,
            Extensions =
            {
                ["code"] = code,
                ["traceId"] = context.TraceIdentifier
            }
        });
    }

    internal static string? ResolveRouteFeature(string? routeValue)
    {
        return SaasFeatureCatalog.ResolveContentImportFeature(routeValue);
    }
}