<?php
/**
 * core/Rbac.php
 * Control de acceso basado en roles (RBAC) con granularidad de acciones.
 * Consolida la lógica de hasModule() / requireAdmin() dispersa en _helpers.php
 * y config.php con soporte para los nuevos campos can_* (Migración 001).
 *
 * USO EN VISTAS:
 *   Rbac::requireLogin();
 *   Rbac::requireModule('lab_domicilios');
 *   if (Rbac::can('lab_reportes', 'export')) { ... }
 *
 * USO EN API (reemplaza funciones sueltas de _helpers.php):
 *   Rbac::requireAdmin();
 *   Rbac::requireModule('turnero', 'create');
 */

class Rbac
{
    // ─── Consulta básica de módulo ───────────────────────────────────────────

    /**
     * Comprueba si el usuario en sesión tiene acceso (cualquier nivel) al módulo.
     * Retrocompatible: admin sin role_id tiene acceso total.
     */
    public static function hasModule(string $slug): bool
    {
        $modules = $_SESSION['admin_user']['modules'] ?? null;

        if ($modules === null) {
            // Sesión legacy: solo admins pasan
            return ($_SESSION['admin_user']['role'] ?? '') === 'admin'
                || ($_SESSION['admin_user']['role'] ?? '') === 'superadmin';
        }

        return in_array($slug, $modules, true);
    }

    /**
     * Comprueba si el usuario tiene una acción específica sobre el módulo.
     *
     * $action: 'view' | 'create' | 'edit' | 'delete' | 'export'
     *
     * Los roles admin y superadmin tienen acceso total a todo.
     */
    public static function can(string $slug, string $action = 'view'): bool
    {
        $role = $_SESSION['admin_user']['role'] ?? 'admin';

        // Superadmin y admin tienen acceso total
        if (in_array($role, ['superadmin', 'admin'], true)) {
            return true;
        }

        // Primero verificar que tiene el módulo
        if (!self::hasModule($slug)) {
            return false;
        }

        // Leer permisos granulares desde sesión (cargados en authenticateUser)
        $perms = $_SESSION['admin_user']['module_permissions'] ?? [];

        // Formato anterior compatible: 'write' / 'read'
        if (isset($perms[$slug]) && is_string($perms[$slug])) {
            $legacy = $perms[$slug];
            if ($action === 'view')   return true; // read implica view
            return $legacy === 'write';
        }

        // Formato nuevo: array con can_* (post Migración 001)
        if (isset($perms[$slug]) && is_array($perms[$slug])) {
            return (bool) ($perms[$slug]["can_{$action}"] ?? false);
        }

        // Sin permisos granulares → view por defecto
        return $action === 'view';
    }

    // ─── Guardas / Requisitos ────────────────────────────────────────────────

    /**
     * Detiene con 403 JSON si el usuario no está autenticado.
     * Para uso en vistas, redirige al login.
     */
    public static function requireLogin(string $redirect = null): void
    {
        if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
            if ($redirect !== null) {
                header("Location: $redirect");
                exit;
            }
            self::jsonForbidden('No autenticado', 401);
        }
    }

    /**
     * Detiene con 403 si el usuario no es admin ni superadmin.
     */
    public static function requireAdmin(): void
    {
        $role = $_SESSION['admin_user']['role'] ?? '';
        if (!in_array($role, ['admin', 'superadmin'], true)) {
            self::jsonForbidden('Acceso restringido a administradores', 403);
        }
    }

    /**
     * Detiene con 403 si el usuario no tiene acceso al módulo.
     * Opcionalmente exige una acción específica.
     */
    public static function requireModule(string $slug, string $action = 'view'): void
    {
        if (!self::can($slug, $action)) {
            self::jsonForbidden("Sin permiso en módulo '{$slug}' ({$action})", 403);
        }
    }

    /**
     * Alias semántico: detiene si no tiene permiso de escritura en el módulo.
     */
    public static function requireWrite(string $slug): void
    {
        self::requireModule($slug, 'edit');
    }

    // ─── Helpers internos ───────────────────────────────────────────────────

    private static function jsonForbidden(string $msg, int $code = 403): void
    {
        // Si ya enviamos headers JSON (contexto API), responder JSON
        if (isset($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json')) {
            http_response_code($code);
            header('Content-Type: application/json; charset=utf-8');
            echo json_encode(['ok' => false, 'error' => $msg], JSON_UNESCAPED_UNICODE);
            exit;
        }
        // Contexto HTML: página de error simple
        http_response_code($code);
        echo "<h1>{$code}</h1><p>" . htmlspecialchars($msg) . "</p>";
        exit;
    }

    // ─── Utilidades de sesión ────────────────────────────────────────────────

    public static function currentRole(): string
    {
        return $_SESSION['admin_user']['role'] ?? 'admin';
    }

    public static function currentModules(): array
    {
        return $_SESSION['admin_user']['modules'] ?? [];
    }

    /**
     * Carga en sesión los permisos granulares (can_*) desde la BD.
     * Se llama desde authenticateUser() después de la Migración 001.
     */
    public static function loadGranularPermissions(int $roleId): array
    {
        try {
            $db = Database::getInstance();
            $rows = $db->fetchAll(
                "SELECT module_slug, can_view, can_create, can_edit, can_delete, can_export, extra_json
                   FROM role_modules
                  WHERE role_id = ?",
                [$roleId]
            );

            $perms = [];
            foreach ($rows as $row) {
                $perms[$row['module_slug']] = [
                    'can_view'   => (bool) $row['can_view'],
                    'can_create' => (bool) $row['can_create'],
                    'can_edit'   => (bool) $row['can_edit'],
                    'can_delete' => (bool) $row['can_delete'],
                    'can_export' => (bool) $row['can_export'],
                    'extra'      => $row['extra_json'] ? json_decode($row['extra_json'], true) : [],
                ];
            }
            return $perms;
        } catch (Exception $e) {
            error_log("[Rbac] Error cargando permisos: " . $e->getMessage());
            return [];
        }
    }
}

// ─── Funciones globales de retrocompatibilidad ──────────────────────────────
// Las vistas y APIs legacy que llaman hasModule(), isEnfermero(), etc.
// siguen funcionando sin cambios apuntando a la clase Rbac.

if (!function_exists('hasModule')) {
    function hasModule(string $slug): bool { return Rbac::hasModule($slug); }
}
if (!function_exists('canDo')) {
    function canDo(string $slug, string $action): bool { return Rbac::can($slug, $action); }
}
