<?php

namespace Middlewares;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use React\Http\Message\Response;
use React\Promise\PromiseInterface;

class CorsMiddleware
{
    private ?string $forcedOrigin;

    public function __construct()
    {
        $origin = $_ENV['CORS_ALLOWED_ORIGIN'] ?? '';
        $this->forcedOrigin = $origin !== '' ? $origin : null;
    }

    public function __invoke(ServerRequestInterface $request, callable $next)
    {
        $origin = $this->resolveOrigin($request);

        if (strtoupper($request->getMethod()) === 'OPTIONS') {
            return $this->preflightResponse($origin);
        }

        $response = $next($request);

        return $this->decorateResponse($response, $origin);
    }

    /**
     * @param ResponseInterface|PromiseInterface $response
     * @return ResponseInterface|PromiseInterface
     */
    private function decorateResponse($response, string $origin)
    {
        if ($response instanceof PromiseInterface) {
            return $response->then(function ($actual) use ($origin) {
                return $this->decorateResponse($actual, $origin);
            });
        }

        if ($response instanceof ResponseInterface) {
            return $this->applyHeaders($response, $origin);
        }

        return $response;
    }

    private function preflightResponse(string $origin): ResponseInterface
    {
        return $this->applyHeaders(
            Response::plaintext('')->withStatus(204),
            $origin
        );
    }

    private function applyHeaders(ResponseInterface $response, string $origin): ResponseInterface
    {
        $response = $response
            ->withHeader('Access-Control-Allow-Origin', $origin)
            ->withHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
            ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
            ->withHeader('Access-Control-Max-Age', '86400')
            ->withHeader('Vary', 'Origin');

        if ($origin !== '*') {
            $response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
        }

        return $response;
    }

    private function resolveOrigin(ServerRequestInterface $request): string
    {
        if ($this->forcedOrigin !== null) {
            return $this->forcedOrigin;
        }

        $origin = $request->getHeaderLine('Origin');
        return $origin !== '' ? $origin : '*';
    }
}
