<?php

declare(strict_types=1);

namespace App\Middleware;

use App\Core\ErrorHandler;
use App\Core\Logger;
use App\Core\MiddlewareInterface;
use App\Core\Request;
use App\Core\Session;

/**
 * RoleMiddleware — Role-Based Portal Access Guard
 *
 * Validates that the authenticated user's `role_snapshot` permits access to
 * the portal path being requested (SPRINT2_PLANNING §5 AUTH-004, §6.2).
 *
 * Role → portal path mapping:
 *   couple      → /app/couple/*
 *   crew        → /app/crew/*
 *   super_admin → /app/admin/*
 *
 * On mismatch: logs a warning and renders 403 Forbidden via ErrorHandler.
 * On match: continues the pipeline ($next is called).
 *
 * CONFLICT RESOLUTION NOTE (docs/ai/memory/33_AUTH004_conflict.md):
 *   The Router.php docblock (§group example) showed a non-normative notation
 *   'RoleMiddleware:couple', suggesting parameterized middleware. This notation
 *   is NOT supported by the frozen MiddlewarePipeline (which uses new $class()
 *   with zero constructor arguments). This middleware therefore infers the
 *   required role from the request path prefix rather than from a constructor
 *   argument. This deviation is documented. A Design Revision Pack is required
 *   to add parameterized middleware support in a future sprint.
 *
 * Architecture rules:
 *   - No SQL in this class.
 *   - No business logic — access control gate only.
 *   - Implements MiddlewareInterface (CORE-001).
 *   - Zero constructor arguments — MiddlewarePipeline uses new $class() (frozen).
 *   - Role is inferred from $request->getPath() — no colon-suffix parsing.
 *   - Assumes AuthMiddleware has already confirmed authentication.
 *
 * Registration:
 *   RoleMiddleware must be registered AFTER AuthMiddleware in the pipeline:
 *     ['App\\Middleware\\AuthMiddleware', 'App\\Middleware\\RoleMiddleware']
 *   This matches the canonical execution order in SPRINT2_EXECUTION_GUIDE §7.
 *
 * @package Inveetaire
 * @ref     SPRINT2_PLANNING.md §5, SPRINT2_EXECUTION_GUIDE.md §6.2 §7, AUTH-004
 */
class RoleMiddleware implements MiddlewareInterface
{
    /**
     * URL path prefix → required role_snapshot value.
     *
     * Checked in prefix order. The path must START WITH the prefix (or equal it)
     * for the role to apply. Keys must not overlap.
     *
     * @var array<string, string>
     */
    private const PATH_ROLE_MAP = [
        '/app/couple'    => 'couple',
        '/app/crew'      => 'crew',
        '/app/admin'     => 'super_admin',
        '/app/developer' => 'super_admin',
    ];

    // ─────────────────────────────────────────────────────────────────────────
    // MiddlewareInterface contract
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Validate the session role_snapshot against the portal path.
     *
     * @param Request  $request The current HTTP request.
     * @param callable $next    The next middleware layer or controller action.
     * @return void
     */
    public function handle(Request $request, callable $next): void
    {
        $path         = $request->getPath();
        $requiredRole = $this->resolveRequiredRole($path);
        $actualRole   = (string) Session::get('role_snapshot', '');

        // ── Could not resolve a required role for this path ───────────────────
        // Safe-fail: if RoleMiddleware is applied to an unrecognised path prefix,
        // the request is denied. This should not occur in correctly registered routes.
        if ($requiredRole === null) {
            Logger::warning('RoleMiddleware: could not resolve required role from path', [
                'path'          => $path,
                'role_snapshot' => $actualRole,
                'user_id'       => Session::has('auth_user_id') ? (int) Session::get('auth_user_id') : null,
            ]);

            ErrorHandler::renderForbidden();
            return;
        }

        // ── Role mismatch ─────────────────────────────────────────────────────
        $isSuperAdminView = ($actualRole === 'super_admin' && Session::get('super_admin_workspace_mode') === true);
        if ($actualRole !== $requiredRole && !$isSuperAdminView) {
            Logger::warning('Authorization failed — role mismatch', [
                'path'          => $path,
                'required_role' => $requiredRole,
                'actual_role'   => $actualRole,
                'user_id'       => Session::has('auth_user_id') ? (int) Session::get('auth_user_id') : null,
            ]);

            ErrorHandler::renderForbidden();
            return;
        }

        // ── Role matches — continue pipeline ──────────────────────────────────
        $next($request);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // PRIVATE — Path Resolution
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Resolve the required role for a given request path.
     *
     * The path must exactly equal a portal prefix or begin with the prefix
     * followed by '/'. This prevents a prefix '/app/couple' from matching
     * the unrelated path '/app/coupleOther'.
     *
     * Returns null when no portal prefix matches the path.
     *
     * @param string $path Application-relative URI path (e.g. '/app/couple/guests').
     * @return string|null  The required role_snapshot value, or null if unknown.
     */
    private function resolveRequiredRole(string $path): ?string
    {
        foreach (self::PATH_ROLE_MAP as $prefix => $role) {
            if ($path === $prefix || strpos($path, $prefix . '/') === 0) {
                return $role;
            }
        }

        return null;
    }
}
