<?php

namespace App\Filters;

use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\ApiCors as ApiCorsConfig;
use Config\Services;

class ApiCors implements FilterInterface
{
    /** @var ApiCorsConfig */
    private $config;

    public function __construct()
    {
        $this->config = config(ApiCorsConfig::class);
    }

    public function before(RequestInterface $request, $arguments = null)
    {
        if (! $this->shouldApply($request)) {
            return null;
        }

        $origin = trim((string) $request->getHeaderLine('Origin'));
        $allowedOrigin = $this->resolveAllowedOrigin($origin);

        // Always vary on Origin to avoid cache mixups.
        $this->emitHeader('Vary', 'Origin');

        if ($allowedOrigin !== null) {
            $this->emitRawHeaders($allowedOrigin);
        }

        if (strtoupper($request->getMethod()) !== 'OPTIONS') {
            return null;
        }

        $response = Services::response();
        $response->setStatusCode(204);

        if ($allowedOrigin !== null) {
            $this->applyHeaders($response, $allowedOrigin);
        }

        // Fast-fail disallowed origins on preflight.
        if ($allowedOrigin === null) {
            return $response->setStatusCode(403)->setJSON([
                'status'  => 'failed',
                'message' => 'CORS origin not allowed',
            ]);
        }

        return $response;
    }

    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
    {
        if (! $this->shouldApply($request)) {
            return $response;
        }

        $origin = trim((string) $request->getHeaderLine('Origin'));
        $allowedOrigin = $this->resolveAllowedOrigin($origin);

        $response->appendHeader('Vary', 'Origin');

        if ($allowedOrigin !== null) {
            $this->applyHeaders($response, $allowedOrigin);
        }

        return $response;
    }

    private function resolveAllowedOrigin(string $origin): ?string
    {
        if ($origin === '') {
            return null;
        }

        return in_array($origin, $this->config->allowedOrigins, true) ? $origin : null;
    }

    private function shouldApply(RequestInterface $request): bool
    {
        $path = ltrim($request->getUri()->getPath(), '/');
        return strpos($path, 'modules/api/v1/') === 0;
    }

    private function applyHeaders(ResponseInterface $response, string $allowedOrigin): void
    {
        $response->setHeader('Access-Control-Allow-Origin', $allowedOrigin);
        $response->setHeader('Access-Control-Allow-Methods', implode(', ', $this->config->allowedMethods));
        $response->setHeader('Access-Control-Allow-Headers', implode(', ', $this->config->allowedHeaders));
        $response->setHeader('Access-Control-Expose-Headers', implode(', ', $this->config->exposedHeaders));
        $response->setHeader('Access-Control-Max-Age', (string) $this->config->maxAge);

        if ($this->config->supportsCredentials) {
            $response->setHeader('Access-Control-Allow-Credentials', 'true');
        }
    }

    /**
     * Emit headers early so 4xx/5xx/fatal responses still carry CORS.
     */
    private function emitRawHeaders(string $allowedOrigin): void
    {
        $this->emitHeader('Access-Control-Allow-Origin', $allowedOrigin);
        $this->emitHeader('Access-Control-Allow-Methods', implode(', ', $this->config->allowedMethods));
        $this->emitHeader('Access-Control-Allow-Headers', implode(', ', $this->config->allowedHeaders));
        $this->emitHeader('Access-Control-Expose-Headers', implode(', ', $this->config->exposedHeaders));
        $this->emitHeader('Access-Control-Max-Age', (string) $this->config->maxAge);

        if ($this->config->supportsCredentials) {
            $this->emitHeader('Access-Control-Allow-Credentials', 'true');
        }
    }

    private function emitHeader(string $name, string $value): void
    {
        if (!headers_sent()) {
            header($name . ': ' . $value, false);
        }
    }
}
