using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.Extensions.Options;

namespace Fast.Shared.Logic;

public class DisableAuthenticationPolicyEvaluator : IPolicyEvaluator
{
    public async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
    {
        // Always pass authentication.
        var claims = new List<Claim> { new Claim(Util.AppUserIdClaimType, FastValues.MasterAppUserId.ToString()) };
        var identity = new ClaimsIdentity(claims, "DisabledAuth");
        var principal = new ClaimsPrincipal(identity);
        var authenticationTicket = new AuthenticationTicket(principal, new AuthenticationProperties(), "Disabled");
        return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
    }

    public async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object? resource)
    {
        //Always pass authorization
        return await Task.FromResult(PolicyAuthorizationResult.Success());
    }
}

public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
    public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
        : base(options)
    {
    }

    public override async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
    {
        var policy = await base.GetPolicyAsync(policyName);
        if (policy == null)
        {
            string[] policyParts = policyName.Split(".");
            string securityActionName = policyParts[0];
            PermissionType permissionType = (PermissionType)Enum.Parse(typeof(PermissionType), policyParts[1]);
            policy = new AuthorizationPolicyBuilder().AddRequirements(
                new PermissionRequirement(securityActionName, permissionType)
            ).Build();

            return await Task.FromResult(policy);
        }
        return policy;
    }
}

public class PermissionAttribute : AuthorizeAttribute
{
    public PermissionAttribute(string securityActionName, PermissionType permissionType)
    {
        Policy = $"{securityActionName}.{permissionType}";
    }
}

public class PermissionRequirement : IAuthorizationRequirement
{
    public string SecurityActionName { get; private set; }
    public PermissionType PermissionType { get; private set; }

    public PermissionRequirement(string securityActionName, PermissionType permissionType)
    {
        SecurityActionName = securityActionName;
        PermissionType = permissionType;
    }
}

public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
    private readonly MyDbContext db;
    public PermissionHandler(MyDbContext context)
    {
        db = context;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        //we don't use IsAuthorized from AuthorizationHelper here since the helper caches and reuses data
        //here we want to re-check the database every time
        bool isAllowed = false;

        if (context?.User?.Claims.Any() == true)
        {
            int appUserId = Util.GetAppUserId(context.User);
            string actionName = requirement.SecurityActionName;
            bool actionExists = (await Main.AuthCache.GetSecurityActionNames()).Contains(actionName);

            if (!actionExists)
                throw new Exception($"The security action [{requirement.SecurityActionName}] was not found.");

            var secGroups = (await Main.AuthCache.GetSecurityGroupsByAppUserId())[appUserId].ToList();

            var authHelper = new AuthorizationHelper(Main.IsAuthenticationEnabled);

            int groupIdx = 0;
            while (!isAllowed && groupIdx < secGroups.Count)
            {
                var secGroup = secGroups[groupIdx];
                var groupPermissionsByActionName = secGroup.SecurityGroupPermissions.ToDictionary(x => x.SecurityAction.Name, StringComparer.OrdinalIgnoreCase);
                bool isGroupDenied = authHelper.GetIsGroupDenied(groupPermissionsByActionName, requirement.PermissionType, actionName);
                if (!isGroupDenied)
                    isAllowed = authHelper.GetIsGroupAllowed(groupPermissionsByActionName, requirement.PermissionType, actionName);

                groupIdx++;
            }

            if (isAllowed)
                context.Succeed(requirement);
        }

        return;
    }
}
