using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Tiku.Application.Security;

namespace Tiku.Api.Security;

public sealed record CurrentTenantMemberRequirement : IAuthorizationRequirement;

public sealed record CurrentPlatformAccessRequirement : IAuthorizationRequirement;

public sealed record TenantPermissionRequirement : IAuthorizationRequirement
{
    public TenantPermissionRequirement(string permissionCode)
    {
        BackendPermissions.EnsureTenant(permissionCode);
        PermissionCode = permissionCode;
    }

    public string PermissionCode { get; }
}

public sealed record PlatformPermissionRequirement : IAuthorizationRequirement
{
    public PlatformPermissionRequirement(string permissionCode)
    {
        BackendPermissions.EnsurePlatform(permissionCode);
        PermissionCode = permissionCode;
    }

    public string PermissionCode { get; }
}

public sealed record AllDataScopeRequirement : IAuthorizationRequirement;

public sealed record TenantResourceAccessRequirement : IAuthorizationRequirement;

public sealed record TenantResourceAuthorizationResource(
    Guid TenantId,
    Guid? OwnerUserId = null,
    Guid? RegionId = null,
    Guid? ClassId = null);

internal sealed class CurrentAccessAuthorizationHandler(ICurrentAccessContext accessContext) :
    AuthorizationHandler<CurrentTenantMemberRequirement>
{
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        CurrentTenantMemberRequirement requirement)
    {
        if (!IsTenantRealm(context.User)) return;

        var access = await accessContext.GetAsync();
        if (access.IsCurrentTenantMember &&
            access.TenantId is { } tenantId &&
            FindTenantId(context.User) == tenantId)
            context.Succeed(requirement);
    }

    internal static bool IsTenantRealm(ClaimsPrincipal principal)
    {
        return string.Equals(principal.FindFirst(TikuClaimTypes.Realm)?.Value, "tenant", StringComparison.Ordinal);
    }

    internal static bool IsPlatformRealm(ClaimsPrincipal principal)
    {
        return string.Equals(principal.FindFirst(TikuClaimTypes.Realm)?.Value, "platform", StringComparison.Ordinal) &&
               principal.FindFirst(TikuClaimTypes.TenantId) is null;
    }

    private static Guid? FindTenantId(ClaimsPrincipal principal)
    {
        return Guid.TryParse(principal.FindFirst(TikuClaimTypes.TenantId)?.Value, out var tenantId) ? tenantId : null;
    }
}

internal sealed class TenantPermissionAuthorizationHandler(
    ICurrentAccessContext accessContext,
    IFeatureAccessService featureAccessService,
    IHttpContextAccessor httpContextAccessor) :
    AuthorizationHandler<TenantPermissionRequirement>
{
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        TenantPermissionRequirement requirement)
    {
        if (!CurrentAccessAuthorizationHandler.IsTenantRealm(context.User)) return;

        var access = await accessContext.GetAsync();
        var operation = IsSafeMethod(httpContextAccessor.HttpContext?.Request.Method)
            ? FeatureAccessOperation.Read
            : FeatureAccessOperation.Write;
        if (access.TenantId is { } tenantId &&
            access.HasTenantPermission(requirement.PermissionCode) &&
            (await featureAccessService.FilterPermissionCodesAsync(
                tenantId, [requirement.PermissionCode], operation)).Contains(requirement.PermissionCode))
            context.Succeed(requirement);
    }

    private static bool IsSafeMethod(string? method)
    {
        return method is not null &&
               (HttpMethods.IsGet(method) || HttpMethods.IsHead(method) || HttpMethods.IsOptions(method));
    }
}

internal sealed class CurrentPlatformAccessAuthorizationHandler(ICurrentAccessContext accessContext) :
    AuthorizationHandler<CurrentPlatformAccessRequirement>
{
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        CurrentPlatformAccessRequirement requirement)
    {
        if (!CurrentAccessAuthorizationHandler.IsPlatformRealm(context.User)) return;

        var access = await accessContext.GetAsync();
        if (access.IsUserActive && access.PlatformPermissions.Count > 0) context.Succeed(requirement);
    }
}

internal sealed class TenantResourceAccessAuthorizationHandler(ICurrentAccessContext accessContext) :
    AuthorizationHandler<TenantResourceAccessRequirement, TenantResourceAuthorizationResource>
{
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        TenantResourceAccessRequirement requirement,
        TenantResourceAuthorizationResource resource)
    {
        if (!CurrentAccessAuthorizationHandler.IsTenantRealm(context.User)) return;

        var access = await accessContext.GetAsync();
        if (access.UserId is { } userId &&
            access.IsCurrentTenantMember &&
            access.TenantId == resource.TenantId &&
            access.DataScope.AllowsResource(userId, resource.OwnerUserId, resource.RegionId, resource.ClassId))
            context.Succeed(requirement);
    }
}

internal sealed class PlatformPermissionAuthorizationHandler(ICurrentAccessContext accessContext) :
    AuthorizationHandler<PlatformPermissionRequirement>
{
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        PlatformPermissionRequirement requirement)
    {
        if (!CurrentAccessAuthorizationHandler.IsPlatformRealm(context.User)) return;

        var access = await accessContext.GetAsync();
        if (access.HasPlatformPermission(requirement.PermissionCode)) context.Succeed(requirement);
    }
}

internal sealed class AllDataScopeAuthorizationHandler(ICurrentAccessContext accessContext) :
    AuthorizationHandler<AllDataScopeRequirement>
{
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        AllDataScopeRequirement requirement)
    {
        var access = await accessContext.GetAsync();
        if (access.IsCurrentTenantMember && access.DataScope.Mode == DataScopeMode.All) context.Succeed(requirement);
    }
}

public static class AccessAuthorizationServiceCollectionExtensions
{
    public static IServiceCollection AddTikuRbacAuthorization(this IServiceCollection services)
    {
        services.AddHttpContextAccessor();
        services.TryAddScoped<IFeatureAccessService, CompatibilityFeatureAccessService>();
        services.AddScoped<IAuthorizationHandler, CurrentAccessAuthorizationHandler>();
        services.AddScoped<IAuthorizationHandler, CurrentPlatformAccessAuthorizationHandler>();
        services.AddScoped<IAuthorizationHandler, TenantPermissionAuthorizationHandler>();
        services.AddScoped<IAuthorizationHandler, PlatformPermissionAuthorizationHandler>();
        services.AddScoped<IAuthorizationHandler, AllDataScopeAuthorizationHandler>();
        services.AddScoped<IAuthorizationHandler, TenantResourceAccessAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy(
                TikuPolicies.CurrentTenantMember,
                policy => policy
                    .RequireAuthenticatedUser()
                    .AddRequirements(new CurrentTenantMemberRequirement()));
            options.AddPolicy(
                TikuPolicies.TenantBackofficeBootstrap,
                policy => policy
                    .RequireAuthenticatedUser()
                    .AddRequirements(new CurrentTenantMemberRequirement()));
            options.AddPolicy(
                TikuPolicies.PlatformBackofficeBootstrap,
                policy => policy
                    .RequireAuthenticatedUser()
                    .AddRequirements(new CurrentPlatformAccessRequirement()));
            // Temporary compatibility for controllers that have not yet been
            // split into their module-specific permission policy. This must
            // remain database-backed; an authenticated-only alias would reopen
            // every legacy tenant administration endpoint to ordinary users.
            options.AddPolicy(
                TikuPolicies.TenantAdmin,
                policy => policy
                    .RequireAuthenticatedUser()
                    .AddRequirements(
                        new CurrentTenantMemberRequirement(),
                        new TenantPermissionRequirement(BackendPermissions.TenantRoleManage)));
            options.AddPolicy(
                TikuPolicies.TenantContentManageAllScope,
                policy => policy
                    .RequireAuthenticatedUser()
                    .AddRequirements(
                        new CurrentTenantMemberRequirement(),
                        new TenantPermissionRequirement(BackendPermissions.TenantContentManage),
                        new AllDataScopeRequirement()));
            options.AddPolicy(
                TikuPolicies.TenantAllDataScope,
                policy => policy
                    .RequireAuthenticatedUser()
                    .AddRequirements(
                        new CurrentTenantMemberRequirement(),
                        new AllDataScopeRequirement()));
            options.AddPolicy(
                TikuPolicies.TenantCommerceOperateAllScope,
                policy => policy
                    .RequireAuthenticatedUser()
                    .AddRequirements(
                        new CurrentTenantMemberRequirement(),
                        new TenantPermissionRequirement(BackendPermissions.TenantCommerceOperate),
                        new AllDataScopeRequirement()));

            foreach (var permissionCode in BackendPermissions.Tenant)
                options.AddPolicy(
                    permissionCode,
                    policy => policy
                        .RequireAuthenticatedUser()
                        .AddRequirements(
                            new CurrentTenantMemberRequirement(),
                            new TenantPermissionRequirement(permissionCode)));

            foreach (var permissionCode in BackendPermissions.Platform)
                options.AddPolicy(
                    permissionCode,
                    policy => policy
                        .RequireAuthenticatedUser()
                        .AddRequirements(new PlatformPermissionRequirement(permissionCode)));
        });

        return services;
    }
}

internal sealed class CompatibilityFeatureAccessService : IFeatureAccessService
{
    public Task<FeatureAccessDecision> EvaluateAsync(Guid tenantId, string featureCode,
        FeatureAccessOperation operation, CancellationToken cancellationToken = default)
    {
        return Task.FromResult(new FeatureAccessDecision(true, null, featureCode, operation));
    }

    public Task<IReadOnlySet<string>> GetEnabledFeaturesAsync(Guid tenantId,
        FeatureAccessOperation operation = FeatureAccessOperation.Read, CancellationToken cancellationToken = default)
    {
        return Task.FromResult<IReadOnlySet<string>>(
            new HashSet<string>(SaasFeatureCatalog.All, StringComparer.Ordinal));
    }

    public Task<IReadOnlySet<string>> FilterPermissionCodesAsync(Guid tenantId, IEnumerable<string> permissionCodes,
        FeatureAccessOperation operation = FeatureAccessOperation.Read, CancellationToken cancellationToken = default)
    {
        return Task.FromResult<IReadOnlySet<string>>(permissionCodes.ToHashSet(StringComparer.Ordinal));
    }

    public Task<IReadOnlyCollection<FeatureQuotaSnapshot>> GetQuotaSummaryAsync(Guid tenantId,
        CancellationToken cancellationToken = default)
    {
        return Task.FromResult<IReadOnlyCollection<FeatureQuotaSnapshot>>([]);
    }

    public Task<bool> TryConsumeQuotaAsync(Guid tenantId, string metricCode, long amount,
        CancellationToken cancellationToken = default)
    {
        return Task.FromResult(true);
    }

    public Task ReleaseQuotaAsync(Guid tenantId, string metricCode, long amount,
        CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }
}