<?php

namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Protection CSRF par vérification de l'origine de la requête.
 *
 * Les mutations de l'app sont déclenchées par des fetch() JSON/multipart same-origin
 * (sans jeton CSRF historiquement). Cette garde rejette toute requête qui modifie l'état
 * et dont on a la preuve qu'elle provient d'une AUTRE origine — y compris un sous-domaine
 * voisin compromis (*.eff-finance-web-projects.fr), que SameSite (Lax ou Strict) ne couvre
 * PAS car il traite tout le domaine enregistrable comme « same-site ».
 *
 * Stratégie (faux positifs quasi nuls) : on ne bloque que sur preuve POSITIVE de cross-origin.
 *  - Sec-Fetch-Site présent et valant cross-site / same-site  → 403
 *  - sinon Origin présent et dont l'hôte diffère de celui de la requête → 403
 *  - sinon (navigateurs anciens sans ces en-têtes) → laissé passer (comportement antérieur)
 *
 * S'applique aux méthodes non sûres (POST/PUT/PATCH/DELETE) ET au logout en GET
 * (qui change l'état → protège du logout-CSRF via <img src=".../logout">).
 */
final class CsrfOriginSubscriber implements EventSubscriberInterface
{
    private const SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'];
    private const LOGOUT_PATH = '/api/auth/logout';

    public static function getSubscribedEvents(): array
    {
        // Priorité élevée : avant les contrôleurs, après la résolution de la requête.
        return [KernelEvents::REQUEST => ['onRequest', 16]];
    }

    public function onRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return;
        }

        $request = $event->getRequest();
        $isStateChanging = !\in_array($request->getMethod(), self::SAFE_METHODS, true)
            || self::LOGOUT_PATH === $request->getPathInfo();

        if (!$isStateChanging) {
            return;
        }

        if ($this->isCrossOrigin($request)) {
            $event->setResponse(new JsonResponse(
                ['error' => 'Origine de la requête non autorisée.'],
                Response::HTTP_FORBIDDEN
            ));
        }
    }

    private function isCrossOrigin(Request $request): bool
    {
        // 1) Sec-Fetch-Site (navigateurs modernes) : signal le plus fiable.
        $secFetchSite = $request->headers->get('Sec-Fetch-Site');
        if (null !== $secFetchSite) {
            return \in_array($secFetchSite, ['cross-site', 'same-site'], true);
        }

        // 2) Repli sur l'en-tête Origin : on compare l'hôte (schéma+host+port) à la cible.
        $origin = $request->headers->get('Origin');
        if (null !== $origin && '' !== $origin && 'null' !== $origin) {
            $originHost = parse_url($origin, PHP_URL_HOST);

            return $originHost !== $request->getHost();
        }

        // 3) Aucun en-tête exploitable → on n'a pas de preuve de cross-origin : laissé passer.
        return false;
    }
}
