<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifyCsrfToken;

class VerifyCsrfToken extends BaseVerifyCsrfToken
{
    /**
     * URIs that may be exempt from CSRF when the request is from a trusted origin (e.g. SPA on different port/domain).
     *
     * @var array<int, string>
     */
    protected $except = [];

    /**
     * Determine if the request has a URI that should be excluded from CSRF verification.
     * Exempt api/login and api/register when Origin or Referer is in the allowed CORS list
     * so the SPA can log in when it cannot send the XSRF cookie (e.g. different origin).
     */
    protected function inExceptArray($request): bool
    {
        if ($request->is('api/login') || $request->is('api/register')) {
            $origin = $this->requestOrigin($request);
            if ($origin !== null && $this->isAllowedOrigin($origin)) {
                return true;
            }
        }

        return parent::inExceptArray($request);
    }

    private function requestOrigin($request): ?string
    {
        $value = $request->header('Referer') ?: $request->header('Origin');
        if (! is_string($value) || $value === '') {
            return null;
        }
        $value = rtrim(trim($value), '/');
        $parsed = parse_url($value);
        if (! isset($parsed['scheme'], $parsed['host'])) {
            return null;
        }
        $scheme = strtolower($parsed['scheme']);
        $host = strtolower($parsed['host']);
        $port = isset($parsed['port']) ? (int) $parsed['port'] : null;
        $path = isset($parsed['path']) && $parsed['path'] !== '/' ? $parsed['path'] : '';
        $defaultPort = $scheme === 'https' ? 443 : 80;
        if ($port === $defaultPort) {
            $port = null;
        }

        return $scheme.'://'.$host.($port !== null ? ':'.$port : '').$path;
    }

    private function isAllowedOrigin(string $origin): bool
    {
        $allowed = $this->allowedOriginsList();
        $normalized = $this->normalizeOrigin($origin);
        foreach ($allowed as $a) {
            if ($this->normalizeOrigin($a) === $normalized) {
                return true;
            }
        }

        return false;
    }

    /**
     * Allowed origins for CSRF exemption (same as CORS). Single source: config/cors.php.
     *
     * @return array<int, string>
     */
    private function allowedOriginsList(): array
    {
        $list = array_map('strval', config('cors.allowed_origins', []));
        $stateful = config('sanctum.stateful', []);
        foreach ($stateful as $domain) {
            $domain = trim((string) $domain);
            if ($domain !== '' && ! str_contains($domain, '://')) {
                $list[] = 'https://'.$domain;
                $list[] = 'http://'.$domain;
            }
        }

        return array_values(array_unique(array_map([$this, 'normalizeOrigin'], $list)));
    }

    private function normalizeOrigin(string $origin): string
    {
        $origin = rtrim(trim($origin), '/');
        $parsed = parse_url($origin);
        if (! isset($parsed['scheme'], $parsed['host'])) {
            return $origin;
        }
        $scheme = strtolower($parsed['scheme']);
        $host = strtolower($parsed['host']);
        $port = isset($parsed['port']) ? (int) $parsed['port'] : null;
        $path = isset($parsed['path']) && $parsed['path'] !== '/' ? $parsed['path'] : '';
        $defaultPort = $scheme === 'https' ? 443 : 80;
        if ($port === $defaultPort) {
            $port = null;
        }

        return $scheme.'://'.$host.($port !== null ? ':'.$port : '').$path;
    }
}
