using System.Security.Claims;
using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;

namespace adas_core.Authentication.Attributes;

public class PermissionAuthorizeAttribute(string source, string? resourceIdHeader = null)
    : AuthorizeAttribute, IAsyncAuthorizationFilter
{
    /// <summary>
    /// Performs asynchronous authorization by validating the user's roles, username, and source header against the permission service.
    /// For the <c>Panel</c> source, it verifies that at least one of the user's roles grants access to the panel; for other sources, it requires a resource identifier header (such as <c>DisplayId</c> or <c>UnitId</c>) and checks access to the corresponding resource.
    /// If any required value is missing or no role grants the needed permission, the response is set to <see cref="ForbidResult"/>.
    /// </summary>
    /// <param name="context">The <see cref="AuthorizationFilterContext"/> for the current authorization request, providing access to the HTTP context, services, and the result to set when authorization fails.</param>
    /// <exception cref="ForbbidenException">Thrown when the <see cref="IPermissionService"/> cannot be resolved from the request services.</exception>
    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            // Obtener el servicio de permisos.
            var permissionService = context.HttpContext.RequestServices.GetService<IPermissionService>()
                                    ?? throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
    
            var user = context.HttpContext.User;
    
            // Obtener el header que indica la fuente.
            var headerSource = context.HttpContext.Request.Headers[source].FirstOrDefault();
    
            // Obtener el identificador del usuario
            var username = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
    
            // Obtiene la lista de roles (valores) del usuario.
            var userRoles = user.Claims
                .Where(c => c.Type == ClaimTypes.Role)
                .Select(c => c.Value)
                .ToList();
    
            // Procesa cada rol: si contiene 2 guiones bajos, elimina el tercer segmento.
            var processedRoles = userRoles.Select(role =>
            {
                var parts = role.Split('_');
                return parts.Length == 2 ? parts[0] : role;
            }).ToList();
    
            // Validar que se tenga al menos un rol, username y headerSource.
            if (!processedRoles.Any() || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(headerSource))
            {
                context.Result = new ForbidResult();
                return;
            }
    
            // Parseamos el header para obtener la fuente.
            var source1 =
                (PermissionEnum.SourcePermissionsEnum)Enum.Parse(typeof(PermissionEnum.SourcePermissionsEnum),
                    headerSource);
    
            var hasPermission = false;
    
            if (source1 == PermissionEnum.SourcePermissionsEnum.Panel)
            {
                // Si la fuente es PANEL, verificamos que alguno de los roles tenga acceso.
                foreach (var roleStr in processedRoles)
                {
                    if (!Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj)) continue;
                    var role = (PermissionEnum.RolesType)roleObj;
                    if (!await permissionService.HasAccessToPanel(username, role, source1)) continue;
                    hasPermission = true;
                    break;
                }
    
                if (!hasPermission) context.Result = new ForbidResult();
            }
            else if (resourceIdHeader != null)
            {
                // Para otras fuentes, se requiere un header que indique el resourceId.
                var resourceId = context.HttpContext.Request.Headers[resourceIdHeader].FirstOrDefault();
                if (string.IsNullOrEmpty(resourceId))
                {
                    context.Result = new ForbidResult();
                    return;
                }
    
                // Verificar que al menos uno de los roles tenga acceso al recurso.
                foreach (var roleStr in processedRoles)
                    if (Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj))
                    {
                        var role = (PermissionEnum.RolesType)roleObj;
                        hasPermission = resourceIdHeader switch
                        {
                            "DisplayId" => await permissionService.HasAccessToDisplay(username, role, source1, resourceId),
                            "UnitId" => await permissionService.HasAccessToUnit(username, role, source1, resourceId),
                            _ => false
                        };
                        if (hasPermission)
                            break;
                    }
    
                if (!hasPermission) context.Result = new ForbidResult();
            }
        }
}