<?php

declare(strict_types=1);

namespace App\Core;

use PDO;

/**
 * Rbac
 *
 * Resolves "can the current user do X?" from three layers, in order:
 *   1. Per-user DENY override (user_permission_overrides) — always wins.
 *   2. Per-user ALLOW override — grants access even if the role doesn't.
 *   3. The user's role's permissions (role_permissions).
 *
 * This is what config/permissions.php's per-module checkboxes compile
 * down to at request time, and it's also what PermissionMiddleware and
 * any view ("show this button only if the user can...") should call.
 */
final class Rbac
{
    /** @return string[] All permission slugs the given user currently holds. */
    public static function permissionsFor(int $userId): array
    {
        static $cache = [];
        if (isset($cache[$userId])) {
            return $cache[$userId];
        }

        $pdo = Database::connection();

        $roleStmt = $pdo->prepare(
            'SELECT p.slug
             FROM users u
             JOIN role_permissions rp ON rp.role_id = u.role_id
             JOIN permissions p ON p.id = rp.permission_id
             WHERE u.id = :id'
        );
        $roleStmt->execute(['id' => $userId]);
        $rolePermissions = $roleStmt->fetchAll(PDO::FETCH_COLUMN);

        $overrideStmt = $pdo->prepare(
            'SELECT p.slug, upo.effect
             FROM user_permission_overrides upo
             JOIN permissions p ON p.id = upo.permission_id
             WHERE upo.user_id = :id'
        );
        $overrideStmt->execute(['id' => $userId]);
        $overrides = $overrideStmt->fetchAll(PDO::FETCH_ASSOC);

        $effective = array_fill_keys($rolePermissions, true);

        foreach ($overrides as $override) {
            $effective[$override['slug']] = $override['effect'] === 'allow';
        }

        return $cache[$userId] = array_keys(array_filter($effective));
    }

    public static function userCan(int $userId, string $permissionSlug): bool
    {
        return in_array($permissionSlug, self::permissionsFor($userId), true);
    }

    /** Convenience wrapper for the currently authenticated user. */
    public static function can(string $permissionSlug): bool
    {
        $id = Auth::id();

        return $id !== null && self::userCan($id, $permissionSlug);
    }

    /** True if the user holds the 'manage' action for the module, OR the specific action requested. */
    public static function canAny(string $module, string ...$actions): bool
    {
        $id = Auth::id();
        if ($id === null) {
            return false;
        }

        $granted = self::permissionsFor($id);

        if (in_array("{$module}.manage", $granted, true)) {
            return true;
        }

        foreach ($actions as $action) {
            if (in_array("{$module}.{$action}", $granted, true)) {
                return true;
            }
        }

        return false;
    }

    public static function clearCache(): void
    {
        // Static local cache is per-request only (PHP has no shared
        // memory between requests without an extension like APCu), so
        // this exists mainly for long-running CLI scripts/tests.
    }
}
