<?php
declare(strict_types=1);

namespace App\Middleware;

use Cake\Controller\Exception\FormProtectionException;
use Cake\Http\Exception\BadRequestException;
use Cake\Http\Exception\InvalidCsrfTokenException;
use Cake\Http\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

/**
 * Converts InvalidCsrfTokenException (and FormProtection tamper exceptions)
 * into a friendly 303 redirect back to the referrer with a flash message.
 *
 * Happens when the user's form token is stale — typical cases:
 *   • server restart between GET and POST (dev)
 *   • multiple old tabs left open across deploys
 *   • browser blocked/cleared the csrfToken cookie mid-session
 *
 * Falling back with "Your session timed out — please try again" is safer than
 * dumping visitors on an error page; the next GET re-mints the cookie + token.
 */
class TokenRecoveryMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        try {
            return $handler->handle($request);
        } catch (InvalidCsrfTokenException | FormProtectionException $e) {
            return $this->recover($request, $e->getMessage());
        } catch (BadRequestException $e) {
            $msg = $e->getMessage();
            if ($this->looksLikeTamperedForm($msg)) {
                return $this->recover($request, $msg);
            }
            throw $e;
        }
    }

    private function recover(ServerRequestInterface $request, string $reason): ResponseInterface
    {
        $referer = (string)$request->getHeaderLine('Referer');
        $target = $referer !== '' ? $referer : '/';

        // Stash a flash so the redirected page shows it.
        $session = $request->getAttribute('session');
        if ($session !== null && method_exists($session, 'write')) {
            $session->write('Flash.flash', [[
                'message' => 'Your session timed out — please try again.',
                'key' => 'flash',
                'element' => 'flash/error',
                'params' => [],
            ]]);
        }

        return (new Response())
            ->withStatus(303)
            ->withHeader('Location', $target);
    }

    private function looksLikeTamperedForm(string $message): bool
    {
        $m = strtolower($message);

        return str_contains($m, 'tampered')
            || str_contains($m, 'unexpected field')
            || str_contains($m, 'missing field')
            || str_contains($m, 'form data')
            || str_contains($m, 'formprotection')
            || str_contains($m, 'form protection');
    }
}
