<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\services;

use apexl\Config\ConfigurationInterface;
use apexl\Io\modules\user\exceptions\JwtException;
use apexl\Io\modules\user\interfaces\JwtKeyProviderInterface;
use apexl\Io\modules\user\interfaces\JwtServiceInterface;
use DateTimeImmutable;
use Exception;
use Firebase\JWT\JWT;
use Psr\Http\Message\ServerRequestInterface;

final readonly class JwtFromRequestService implements JwtServiceInterface
{
    public function __construct(
        private JwtKeyProviderInterface $keyProvider,
        private ServerRequestInterface $request,
        private ConfigurationInterface $config,
    ) {}

    /**
     * @throws JwtException
     */
    public function token(): object
    {
        $tokenFromAuthHeader = $this->tokenFromAuthHeader();

        try {
            $token = JWT::decode(
                $tokenFromAuthHeader,
                $this->keyProvider->key()
            );

            $this->assertTokenIsValid($token);

            return $token;
        } catch (Exception $exception) {
            throw new JwtException($exception->getMessage(), $exception->getCode(), $exception);
        }

    }

    /**
     * @throws JwtException
     */
    private function tokenFromAuthHeader(): string
    {
        if (preg_match(
            '/^Bearer (\S+)/',
            $this->authHeader(),
            $matches,
        )) {
            return $matches[1];
        }

        throw new JwtException('Authorization header pattern not recognized');
    }

    /**
     * @throws JwtException
     */
    private function authHeader(): string
    {
        if ($this->request->hasHeader('Authorization')) {
            return $this->request->getHeader('Authorization')[0];
        }

        throw new JwtException('Authorization header not found');
    }

    /**
     * @throws JwtException
     */
    private function assertTokenIsValid(object $token): void
    {
        $serverName = $this->serverName();
        $now = new DateTimeImmutable();

        if (
            $token->iss === $serverName
            && $token->nbf <= $now->getTimestamp() && $token->exp >= $now->getTimestamp()
            && !empty($token->sessionId)
            && isset($token->userId)
            && $token->userId > 0
        ) {
            return;
        }

        throw new JwtException('JWT is not valid');
    }

    /**
     * The expected issuer. Must mirror JwtCreator's issuer resolution exactly:
     * SERVER_NAME when present, else the configured backend domain's host, else
     * 'localhost'. Previously this threw when SERVER_NAME was unset (CLI / some
     * SAPIs + test runners), rejecting every otherwise-valid token — the latent
     * prod bug and flaky-test source called out in Plan 007 §6.
     */
    private function serverName(): string
    {
        return $_SERVER['SERVER_NAME'] ?? $this->fallbackIssuer();
    }

    private function fallbackIssuer(): string
    {
        if ($this->config->has('http.backend_domain')) {
            $host = parse_url((string) $this->config->get('http.backend_domain'), PHP_URL_HOST);
            if (is_string($host) && $host !== '') {
                return $host;
            }
        }

        return 'localhost';
    }
}
