<?php
// include/AuthMiddleware.php

class AuthMiddleware
{
    private PermissionManager $perm_manager;

    public function __construct(PermissionManager $permission_manager)
    {
        $this->perm_manager = $permission_manager;
    }

    // ──────────────────────────────────────────────────────────────────────────
    // SESSION
    // ──────────────────────────────────────────────────────────────────────────

    /**
     * Start session only if not already active.
     */
    private function startSession(): void
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
    }

    // ──────────────────────────────────────────────────────────────────────────
    // JSON GUARDS  (for AJAX / API endpoints)
    // These respond with HTTP + JSON and exit — never redirect.
    // ──────────────────────────────────────────────────────────────────────────

    /**
     * Require authentication.
     * Returns the authenticated user ID on success.
     * Sends 401 JSON and exits on failure.
     */
    public function requireAuth(): int
    {
        $this->startSession();

        if (empty($_SESSION['user_id'])) {
            $this->jsonError(401, 'Unauthorized', 'Please login to continue');
        }

        return (int) $_SESSION['user_id'];
    }

    /**
     * Require a specific permission.
     * Returns the authenticated user ID on success.
     * Sends 403 JSON and exits on failure.
     *
     * @param string   $permissionName
     * @param int|null $resourceId    Optional – passed to hasPermission for future fine-grained checks.
     * @param int|null $departmentId  Optional – same as above.
     */
    public function requirePermission(
        string $permissionName,
        ?int   $resourceId   = null,
        ?int   $departmentId = null
    ): int {
        $userId = $this->requireAuth();

        if (!$this->perm_manager->hasPermission($userId, $permissionName, $resourceId, $departmentId)) {
            $this->jsonError(403, 'Forbidden', 'You do not have permission to perform this action', [
                'required_permission' => $permissionName,
            ]);
        }

        return $userId;
    }

    /**
     * Require ANY of the given permissions.
     * Returns the authenticated user ID on success.
     * Sends 403 JSON and exits on failure.
     *
     * @param string[] $permissions
     */
    public function requireAnyPermission(array $permissions): int
    {
        $userId = $this->requireAuth();

        if (!$this->perm_manager->userHasAnyPermission($userId, $permissions)) {
            $this->jsonError(403, 'Forbidden', 'You do not have permission to perform this action', [
                'required_permissions' => $permissions,
            ]);
        }

        return $userId;
    }

    /**
     * Require ALL of the given permissions.
     * Returns the authenticated user ID on success.
     * Sends 403 JSON and exits on failure.
     *
     * @param string[] $permissions
     */
    public function requireAllPermissions(array $permissions): int
    {
        $userId = $this->requireAuth();

        foreach ($permissions as $permission) {
            if (!$this->perm_manager->hasPermission($userId, $permission)) {
                $this->jsonError(403, 'Forbidden', 'You do not have all required permissions', [
                    'missing_permission' => $permission,
                ]);
            }
        }

        return $userId;
    }

    /**
     * Role-based access control (JSON guard).
     * Returns the authenticated user ID on success.
     * Sends 403 JSON and exits on failure.
     *
     * @param string|string[] $roleNames
     */
    public function requireRole(array|string $roleNames): int
    {
        $userId   = $this->requireAuth();
        $userRole = $this->perm_manager->getUserRole($userId);
        $roleNames = (array) $roleNames;

        if (!$userRole || !in_array($userRole['name'], $roleNames, true)) {
            $this->jsonError(403, 'Forbidden', 'This action requires role: ' . implode(' or ', $roleNames));
        }

        return $userId;
    }

    /**
     * Department access control (JSON guard).
     * Returns the authenticated user ID on success.
     * Sends 403 JSON and exits on failure.
     */
    public function requireDepartmentAccess(int $departmentId): int
    {
        $userId = $this->requireAuth();

        if (!$this->perm_manager->hasDepartmentAccess($userId, $departmentId)) {
            $this->jsonError(403, 'Forbidden', 'You do not have access to this department');
        }

        return $userId;
    }

    // ──────────────────────────────────────────────────────────────────────────
    // REDIRECT GUARDS  (for full HTML page controllers)
    // These redirect to a URL and exit instead of returning JSON.
    // Use these at the top of admin/page .php files.
    // ──────────────────────────────────────────────────────────────────────────

    /**
     * Require authentication for a page.
     * Redirects to $loginUrl on failure.
     * Returns the authenticated user ID on success.
     */
    public function requireAuthOrRedirect(string $loginUrl = '../login.php'): int
    {
        $this->startSession();

        if (empty($_SESSION['user_id'])) {
            header("Location: {$loginUrl}");
            exit();
        }

        return (int) $_SESSION['user_id'];
    }

    /**
     * Require a specific permission for a page.
     * Redirects to $redirectTo on failure.
     * Returns the authenticated user ID on success.
     *
     * Usage in page controllers:
     *   $auth->requirePermissionOrRedirect('manage_permissions');
     *
     * @param string   $permissionName
     * @param string   $redirectTo     Where to send unauthorised users.
     * @param string   $loginUrl       Where to send unauthenticated users.
     * @param int|null $resourceId
     * @param int|null $departmentId
     */
    public function requirePermissionOrRedirect(
        string $permissionName,
        string $redirectTo   = 'index',
        string $loginUrl     = '../login.php',
        ?int   $resourceId   = null,
        ?int   $departmentId = null
    ): int {
        $userId = $this->requireAuthOrRedirect($loginUrl);

        if (!$this->perm_manager->hasPermission($userId, $permissionName, $resourceId, $departmentId)) {
            header("Location: {$redirectTo}?error=forbidden&required=" . urlencode($permissionName));
            exit();
        }

        return $userId;
    }

    /**
     * Require ANY of the given permissions for a page.
     * Redirects on failure.
     *
     * @param string[] $permissions
     */
    public function requireAnyPermissionOrRedirect(
        array  $permissions,
        string $redirectTo = 'index',
        string $loginUrl   = '../login.php'
    ): int {
        $userId = $this->requireAuthOrRedirect($loginUrl);

        if (!$this->perm_manager->userHasAnyPermission($userId, $permissions)) {
            header("Location: {$redirectTo}?error=forbidden");
            exit();
        }

        return $userId;
    }

    /**
     * Require ALL of the given permissions for a page.
     * Redirects on failure.
     *
     * @param string[] $permissions
     */
    public function requireAllPermissionsOrRedirect(
        array  $permissions,
        string $redirectTo = 'index',
        string $loginUrl   = '../login.php'
    ): int {
        $userId = $this->requireAuthOrRedirect($loginUrl);

        if (!$this->perm_manager->userHasAllPermissions($userId, $permissions)) {
            header("Location: {$redirectTo}?error=forbidden");
            exit();
        }

        return $userId;
    }

    /**
     * Require a specific role for a page.
     * Redirects on failure.
     *
     * @param string|string[] $roleNames
     */
    public function requireRoleOrRedirect(
        array|string $roleNames,
        string $redirectTo = 'index',
        string $loginUrl   = '../login.php'
    ): int {
        $userId   = $this->requireAuthOrRedirect($loginUrl);
        $userRole = $this->perm_manager->getUserRole($userId);
        $roleNames = (array) $roleNames;

        if (!$userRole || !in_array($userRole['name'], $roleNames, true)) {
            header("Location: {$redirectTo}?error=forbidden");
            exit();
        }

        return $userId;
    }

    // ──────────────────────────────────────────────────────────────────────────
    // BOOLEAN CHECKS  (non-terminating helpers for conditional UI logic)
    // ──────────────────────────────────────────────────────────────────────────

    /**
     * Return true/false without redirecting or exiting.
     * Useful for conditionally showing/hiding UI elements.
     *
     * Usage:  if ($auth->can('manage_rotations')) { ... }
     */
    public function can(string $permissionName): bool
    {
        $this->startSession();
        $userId = (int) ($_SESSION['user_id'] ?? 0);

        if (!$userId) {
            return false;
        }

        // Use fast session cache when available
        if (isset($_SESSION['permissions'])) {
            return PermissionManager::sessionCan($permissionName);
        }

        return $this->perm_manager->hasPermission($userId, $permissionName);
    }

    /**
     * Return true if the user has ANY of the given permissions.
     */
    public function canAny(array $permissionNames): bool
    {
        $this->startSession();
        $userId = (int) ($_SESSION['user_id'] ?? 0);

        if (!$userId) {
            return false;
        }

        if (isset($_SESSION['permissions'])) {
            return PermissionManager::sessionCanAny($permissionNames);
        }

        return $this->perm_manager->userHasAnyPermission($userId, $permissionNames);
    }

    /**
     * Return true if the user has ALL of the given permissions.
     */
    public function canAll(array $permissionNames): bool
    {
        $this->startSession();
        $userId = (int) ($_SESSION['user_id'] ?? 0);

        if (!$userId) {
            return false;
        }

        if (isset($_SESSION['permissions'])) {
            return PermissionManager::sessionCanAll($permissionNames);
        }

        return $this->perm_manager->userHasAllPermissions($userId, $permissionNames);
    }

    /**
     * Return true if the current user has the given role name.
     */
    public function hasRole(string|array $roleNames): bool
    {
        $this->startSession();
        $userId = (int) ($_SESSION['user_id'] ?? 0);

        if (!$userId) {
            return false;
        }

        $userRole  = $this->perm_manager->getUserRole($userId);
        $roleNames = (array) $roleNames;

        return $userRole && in_array($userRole['name'], $roleNames, true);
    }

    // ──────────────────────────────────────────────────────────────────────────
    // INTERNAL HELPERS
    // ──────────────────────────────────────────────────────────────────────────

    /**
     * Send a JSON error response and terminate execution.
     *
     * @param int    $httpCode  HTTP status code.
     * @param string $error     Short error label (e.g. 'Forbidden').
     * @param string $message   Human-readable message.
     * @param array  $extra     Any extra key/value pairs to merge into the body.
     */
    private function jsonError(int $httpCode, string $error, string $message, array $extra = []): never
    {
        header('Content-Type: application/json');
        http_response_code($httpCode);
        echo json_encode(array_merge([
            'success' => false,
            'error'   => $error,
            'message' => $message,
        ], $extra));
        exit();
    }
}