<?php

namespace classes;

class cors_policy
{
    public const ALLOWED_HEADERS = 'Content-Type, Authorization, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version, Cache-Control, Pragma, *';
    public const ALLOWED_METHODS = 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
    public const EXPOSED_HEADERS = 'Server-Timing';
    public const MAX_AGE_SECONDS = '86400';

    private const REQUIRED_ALLOWED_ORIGINS = [
        'https://truckwash.io',
        'https://www.truckwash.io',
        'https://api.truckwash.io',
        'https://api.truckwash.io:4433',
        'https://api-v2.truckwash.io',
        'https://web.truckwash.dk',
        'https://api.truckwash.dk',
        'https://truckwash.dk',
        'https://www.truckwash.dk',
        'https://staging.truckwash.io',
        'http://localhost',
        'https://localhost',
        'http://localhost:4433',
        'https://localhost:4433',
        'https://twdev.jeppeb.dk',
        'http://localhost:5173',
        'http://localhost:5174',
        'http://127.0.0.1:5173',
        'http://127.0.0.1:5174',
        'capacitor://localhost',
    ];

    public static function normalizeOrigin(?string $value): string
    {
        $value = trim((string)$value);
        if ($value === '' || $value === '*') {
            return $value;
        }

        if (preg_match('#^[a-z][a-z0-9+.-]*://#i', $value) !== 1) {
            return '';
        }

        $parts = parse_url($value);
        if (!is_array($parts) || empty($parts['scheme']) || empty($parts['host'])) {
            return '';
        }

        $scheme = strtolower((string)$parts['scheme']);
        if (!in_array($scheme, ['http', 'https', 'capacitor'], true)) {
            return '';
        }

        $host = strtolower((string)$parts['host']);
        $port = isset($parts['port']) ? ':' . (int)$parts['port'] : '';

        return $scheme . '://' . $host . $port;
    }

    public static function normalizeRequestOrigin(?string $value): string
    {
        $value = trim((string)$value);
        if ($value === '' || $value === '*') {
            return '';
        }

        $parts = parse_url($value);
        if (!is_array($parts)) {
            return '';
        }

        foreach (['user', 'pass', 'path', 'query', 'fragment'] as $disallowedPart) {
            if (array_key_exists($disallowedPart, $parts)) {
                return '';
            }
        }

        return self::normalizeOrigin($value);
    }

    /**
     * @return array<int,string>
     */
    public static function requiredAllowedOrigins(): array
    {
        return self::REQUIRED_ALLOWED_ORIGINS;
    }

    /**
     * @return array<int,string>
     */
    public static function allowedOrigins(string $corsConfig): array
    {
        $origins = [];
        foreach (self::splitOrigins($corsConfig) as $configuredOrigin) {
            if ($configuredOrigin === '*') {
                return ['*'];
            }

            $origin = self::normalizeOrigin($configuredOrigin);
            if ($origin !== '') {
                $origins[$origin] = true;
            }
        }

        foreach (self::REQUIRED_ALLOWED_ORIGINS as $requiredOrigin) {
            $origin = self::normalizeOrigin($requiredOrigin);
            if ($origin !== '') {
                $origins[$origin] = true;
            }
        }

        return array_keys($origins);
    }

    public static function withRequiredOrigins(string $corsConfig): string
    {
        $allowedOrigins = self::allowedOrigins($corsConfig);
        if ($allowedOrigins === ['*']) {
            return '*';
        }

        return implode(',', $allowedOrigins);
    }

    public static function isOriginAllowed(?string $origin, string $corsConfig): bool
    {
        $origin = self::normalizeRequestOrigin($origin);
        if ($origin === '') {
            return false;
        }

        $allowedOrigins = self::allowedOrigins($corsConfig);
        return in_array('*', $allowedOrigins, true) || in_array($origin, $allowedOrigins, true);
    }

    /**
     * @return array<string,string>
     */
    public static function responseHeaders(?string $origin, string $corsConfig): array
    {
        $origin = self::normalizeRequestOrigin($origin);
        if ($origin === '' || !self::isOriginAllowed($origin, $corsConfig)) {
            return [];
        }

        return [
            'Access-Control-Allow-Origin' => $origin,
            'Access-Control-Allow-Credentials' => 'true',
            'Access-Control-Allow-Headers' => self::ALLOWED_HEADERS,
            'Access-Control-Allow-Methods' => self::ALLOWED_METHODS,
            'Access-Control-Expose-Headers' => self::EXPOSED_HEADERS,
            'Access-Control-Max-Age' => self::MAX_AGE_SECONDS,
            'Timing-Allow-Origin' => $origin,
            'Vary' => 'Origin',
        ];
    }

    /**
     * @return array{allowed:bool,status:int,headers:array<string,string>,body:string}
     */
    public static function preflightResponse(?string $origin, string $corsConfig): array
    {
        $headers = self::responseHeaders($origin, $corsConfig);
        if ($headers === []) {
            return [
                'allowed' => false,
                'status' => 403,
                'headers' => ['Content-Type' => 'application/json'],
                'body' => json_encode(['success' => false, 'message' => 'CORS origin not allowed']) ?: '',
            ];
        }

        $headers['Content-Type'] = 'application/json';
        return [
            'allowed' => true,
            'status' => 200,
            'headers' => $headers,
            'body' => '',
        ];
    }

    public static function applyResponseHeaders(string $corsConfig, ?string $origin = null): bool
    {
        $headers = self::responseHeaders($origin ?? ($_SERVER['HTTP_ORIGIN'] ?? ''), $corsConfig);
        if ($headers === []) {
            return false;
        }

        self::emitHeaders($headers);
        return true;
    }

    /**
     * @param array<string,string> $headers
     */
    public static function emitHeaders(array $headers): void
    {
        foreach ($headers as $name => $value) {
            header($name . ': ' . $value, strtolower((string)$name) !== 'vary');
        }
    }

    /**
     * @return array<int,string>
     */
    private static function splitOrigins(string $corsConfig): array
    {
        return array_values(array_filter(
            array_map('trim', explode(',', $corsConfig)),
            static fn(string $origin): bool => $origin !== ''
        ));
    }
}
