<?php

declare(strict_types=1);

namespace apexl\Io\middleware;

use apexl\Config\Singleton as Config;
use apexl\Io\includes\Hook;
use apexl\Io\includes\System;
use apexl\Io\services\Output;
use Http\Factory\Guzzle\ResponseFactory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Throwable;

final readonly class addCorsHeaders implements MiddlewareInterface
{
    private Config $config;
    private ServerRequestInterface $request;

    public function __construct()
    {
        $this->config = Config::getInstance();
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $this->request = $request;

        try {
            $response = $handler->handle($request);
        } catch (Throwable $throwable) {
            return $this->handleThrowable($throwable);
        }

        return $this->maybeAddCorsHeaders($response);
    }

    private function handleThrowable(Throwable $throwable): ResponseInterface
    {
        error_log(
            sprintf(
                '[IO Exception :: Response] (Line %d | %s) - %s | %s',
                $throwable->getLine(),
                $throwable->getFile(),
                $throwable->getCode(),
                $throwable->getMessage()
            )
        );

        Hook::processHook('error', $throwable);
        Output::addSystemError($throwable);

        $response = (new ResponseFactory())->createResponse();

        return System::asJson($this->maybeAddCorsHeaders($response), [], 500);
    }

    private function maybeAddCorsHeaders(ResponseInterface $response): ResponseInterface
    {
        $referer = $this->refererFromRequest($this->request);

        if ($referer === null && $this->config->app->environment == 'development') {
            // Assume Postman for now, so allow it.
            return $this->addCorsHeaders($response);
        }

        if ($domain = $this->matchRefererToAllowedDomains($referer ?? '')) {
            return $this->addCorsHeaders($response, $domain);
        }

        return $response;
    }

    private function refererFromRequest(RequestInterface $request): ?string
    {
        return $request->getHeader('HTTP_REFERER')[0] ?? null;
    }

    public function addCorsHeaders(ResponseInterface $response, string $domain = "*"): ResponseInterface
    {
        return $response
            ->withHeader('Access-Control-Allow-Origin', $domain)
            ->withHeader(
                'Access-Control-Allow-Headers',
                'X-Requested-With, Content-Type, Accept, Origin, Authorization'
            )
            ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS')
            ->withHeader('Access-Control-Allow-Credentials', $domain === '*' ? 'false' : 'true');
    }

    private function matchRefererToAllowedDomains(string $referer): ?string
    {
        $allowedDomains = $this->config->app->allowedDomains;

        $ix = array_search(trim($referer, '/'), $allowedDomains);

        return $ix === false ? null : $allowedDomains[$ix];
    }

}
