using System.Collections.Concurrent;
using System.Reflection;
using foodmarket.Api.Infrastructure.Tenancy;
using foodmarket.Domain.Organizations;
using foodmarket.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace foodmarket.Api.Infrastructure.Authorization;

/// <summary>Проверяет <see cref="PermissionRequirement"/> по флагам роли
/// организации (RolePermissions) текущего пользователя.
///
/// Логика:
/// <list type="number">
/// <item>SuperAdmin — полный платформенный доступ.</item>
/// <item>Identity-роль Admin — системная роль «Администратор» = RolePermissions.All().
/// Шорткат, чтобы не зависеть от наличия идеально засиженной Employee-записи
/// (custom-роли НЕ маппятся на Identity Admin — см. IdentityRoleMapper —
/// поэтому шорткат их не задевает).</item>
/// <item>Иначе — ищем активного Employee пользователя в его орге и читаем
/// флаг права из его роли. Нет Employee/нет флага → доступ запрещён (fail-closed).</item>
/// </list>
///
/// Регистрируется scoped: использует <see cref="AppDbContext"/>.</summary>
public sealed class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
    private static readonly ConcurrentDictionary<string, PropertyInfo?> PropertyCache = new();

    private readonly AppDbContext _db;

    public PermissionAuthorizationHandler(AppDbContext db) => _db = db;

    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var user = context.User;

        if (user.IsInRole(HttpContextTenantContext.SuperAdminRole) || user.IsInRole("Admin"))
        {
            context.Succeed(requirement);
            return;
        }

        var subRaw = user.FindFirst(Claims.Subject)?.Value;
        var orgRaw = user.FindFirst(HttpContextTenantContext.OrganizationClaim)?.Value;
        if (!Guid.TryParse(subRaw, out var userId) || !Guid.TryParse(orgRaw, out var orgId))
        {
            return; // нет sub/org — не можем установить роль → запрет
        }

        // Решение об авторизации не должно зависеть от ambient tenant-фильтра:
        // фильтруем оргой из claim явно.
        var role = await _db.Employees.IgnoreQueryFilters()
            .Where(e => e.UserId == userId && e.OrganizationId == orgId && e.IsActive && !e.IsDeleted)
            .Select(e => e.Role)
            .FirstOrDefaultAsync();

        if (role is not null && HasPermission(role.Permissions, requirement.Permission))
        {
            context.Succeed(requirement);
        }
    }

    /// <summary>Читает boolean-флаг права по имени через рефлексию (с кэшем
    /// PropertyInfo). Неизвестное имя или не-bool → false (fail-closed).</summary>
    private static bool HasPermission(RolePermissions permissions, string permissionName)
    {
        var prop = PropertyCache.GetOrAdd(permissionName, name =>
            typeof(RolePermissions).GetProperty(name,
                BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase));

        return prop is not null
            && prop.PropertyType == typeof(bool)
            && prop.GetValue(permissions) is true;
    }
}
