<?php
    namespace Workbloom\Services;

    use DateTime;
    use Firebase\JWT\JWT;
    use Firebase\JWT\Key;
    use Workbloom\Config\Config;

    class JWTService {
        private static string $issuer;
        private static int $expiration;
        private static string $algorithm;
        private static string $secretKey;

        /**
         * Inicializa as configurações para a geração de tokens JWT.
         *
         * Este método estático carrega as informações necessárias para a criação e validação de tokens
         * JWT (JSON Web Tokens) a partir de uma classe de configuração (`Config`). Ele busca a chave secreta,
         * o algoritmo de criptografia, o emissor e o tempo de expiração definidos para a aplicação
         * e os armazena como propriedades estáticas da classe.
         *
         * Este método deve ser chamado antes de qualquer operação que envolva a geração ou
         * verificação de tokens, garantindo que as configurações estejam prontas para uso.
         *
         * @return void
         */
        public static function init(): void {
            $Config = Config::getInstance();
            self::$issuer = $Config->get(key: 'JWT_ISSUER');
            self::$algorithm = $Config->get(key: 'JWT_ALGO');
            self::$secretKey = $Config->get(key: 'JWT_SECRET');
            self::$expiration = $Config->get(key: 'JWT_EXPIRATION');
        }

        /**
         * Gera um token JWT com base em um payload e nas configurações da aplicação.
         *
         * Este método cria um token JWT válido a partir de um array de dados fornecido (`$payload`),
         * adicionando automaticamente metadados essenciais como a data de emissão, a data de expiração e o emissor.
         *
         * O processo detalhado inclui:
         * 1.  **Inicialização:** Chama o método `init()` para garantir que todas as configurações do JWT
         * (chave secreta, algoritmo, etc.) estejam carregadas.
         * 2.  **Definição dos Timestamps:** Calcula o timestamp de emissão (`iat`) e o de expiração (`exp`)
         * com base no tempo de expiração definido nas configurações.
         * 3.  **Montagem do Payload:** Adiciona as chaves `iat`, `exp` e `iss` (emissor) ao array `$payload`.
         * 4.  **Codificação do Token:** Utiliza a biblioteca `JWT::encode()` para codificar o payload
         * com a chave secreta e o algoritmo definidos nas configurações.
         *
         * @param array $payload Um array associativo contendo os dados personalizados que serão incluídos no token.
         * @return array O token JWT gerado como um array.
         */
        public static function generateToken(array $payload): array {
            // Carrega as informações do arquivo de configuração
            self::init();

            // Define data de emissão e calcula a data de expiração do token
            $expiration = self::$expiration;
            $createdAt = (new DateTime())->getTimestamp();
            $expireAt = (new DateTime())->modify(modifier: "+{$expiration} seconds")->getTimestamp();

            // Adiciona data de criação ao payload
            $payload['iat'] = $createdAt;
            // Adiciona a expiração ao payload
            $payload['exp'] = $expireAt;
            // Adiciona o emissor ao payload
            $payload['iss'] = self::$issuer;

            return [
                'token' => JWT::encode(payload: $payload, key: self::$secretKey, alg: self::$algorithm),
                'expires_at' => (new DateTime())->setTimestamp(timestamp: $expireAt)->format(format: 'Y-m-d H:i:s')
            ];
        }

        /**
         * Valida um token JWT e retorna o objeto de payload decodificado.
         *
         * Este método estático decodifica e valida um token JWT fornecido, utilizando
         * a chave secreta e o algoritmo de criptografia configurados na classe.
         *
         * #### Como funciona:
         * 1.  Recebe o token (`$token`) que precisa ser validado.
         * 2.  Usa a biblioteca `JWT::decode()` para decodificar o token.
         * 3.  Para a decodificação, ele cria uma nova instância de `Key`, passando
         * a chave secreta (`self::$secretKey`) e o algoritmo (`self::$algorithm`).
         * Isso garante que o token só será decodificado se tiver sido assinado com a chave e algoritmo corretos.
         * 4.  Se o token for válido e a assinatura corresponder, a função retorna o payload do token
         * como um objeto.
         * 5.  Se o token for inválido (por exemplo, assinatura incorreta, expirado ou formato incorreto),
         * a biblioteca JWT lançará uma exceção, que a função que chama este método deve capturar.
         *
         * @param string $token O token JWT a ser validado e decodificado.
         * @return object O objeto de payload decodificado do token.
         */
        public static function validateToken(string $token): object {
            // Inicializa as configurações se ainda não estiverem definidas
            if (!isset(self::$secretKey)) {
                self::init();
            }
            
            return JWT::decode(jwt: $token, keyOrKeyArray: new Key(keyMaterial: self::$secretKey, algorithm: self::$algorithm));
        }

        /**
         * Decodifica um token JWT e retorna seu payload como um array associativo.
         *
         * Este método estático é responsável por decodificar e validar um token JWT. Antes de decodificar, ele garante que as configurações de chave secreta e algoritmo (`self::$secretKey` e `self::$algorithm`) já foram inicializadas chamando o método `init()` se necessário.
         *
         * #### Como Funciona:
         * 1.  **Inicialização:** Primeiro, verifica se a chave secreta (`self::$secretKey`) já foi carregada. Se não, ele chama `self::init()` para carregar as configurações de um arquivo externo.
         * 2.  **Decodificação:** Usa a biblioteca `Firebase\JWT\JWT::decode()` para decodificar o token fornecido. O método passa a chave secreta e o algoritmo para verificar a assinatura do token.
         * 3.  **Conversão:** O payload decodificado, que por padrão é um objeto, é convertido para uma string JSON e depois para um array associativo, o que facilita o acesso aos dados.
         * 4.  **Retorno:** Retorna o payload do token como um array associativo. Se o token for inválido (assinatura incorreta, expirado, etc.), a biblioteca JWT lançará uma exceção, que a função que chama este método deve capturar.
         *
         * @param string $token O token JWT a ser decodificado.
         * @return array O payload do token decodificado como um array associativo.
         */
        public static function decode(?string $token): array {
            // Inicializa as configurações se ainda não estiverem definidas
            if (!isset(self::$secretKey)) {
                self::init();
            }

            if($token === null) {
                return [];
            }

            $decoded = JWT::decode(jwt: $token, keyOrKeyArray: new Key(keyMaterial: self::$secretKey, algorithm: self::$algorithm));
            return json_decode(json: json_encode(value: $decoded), associative: true);
        }

        /**
         * Extrai o token de autenticação do tipo 'Bearer' do cabeçalho da requisição HTTP.
         *
         * Este método estático é um utilitário projetado para buscar um token JWT (JSON Web Token)
         * que é enviado no cabeçalho `Authorization`. Ele é robusto o suficiente para verificar
         * o token em diferentes superglobais do PHP, dependendo da configuração do servidor web.
         *
         * #### Fluxo de Busca:
         * 1.  Primeiro, tenta obter o cabeçalho `Authorization` diretamente da superglobal `$_SERVER`.
         * 2.  Em seguida, tenta a chave `HTTP_AUTHORIZATION`, que é um nome de variável comum em alguns ambientes.
         * 3.  Por último, usa a função `apache_request_headers()` para tentar obter o cabeçalho, caso as 
         * superglobais não estejam preenchidas. Isso garante compatibilidade com servidores Apache onde o cabeçalho pode não ser exposto.
         * 4.  Se o cabeçalho for encontrado, o valor é sanitizado (removendo espaços em branco) e a busca continua.
         * 5.  Se nenhum cabeçalho de autenticação for encontrado, o método retorna `null`.
         *
         * #### Extração do Token:
         * - Depois de encontrar o cabeçalho, o método usa uma expressão regular (`/Bearer\s(\S+)/`)
         * para verificar se o valor do cabeçalho está no formato `Bearer <token>`.
         * - Se o padrão corresponder, ele extrai e retorna a string do token.
         * - Se o padrão não corresponder, o método retorna `null`.
         *
         * @return string|null O token de autenticação do tipo 'Bearer' como uma string, ou `null` 
         * se o cabeçalho não for encontrado ou não estiver no formato esperado.
         */
        public static function getBearerToken(): ?string {
            if (!isset(self::$secretKey)) {
                self::init();
            }

            // Tenta pegar o header de todas as fontes possíveis
            $headers = $_SERVER['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? (function_exists(function: 'apache_request_headers') ? apache_request_headers() : []);

            // Se veio do apache_request_headers, normaliza chaves
            if (is_array(value: $headers)) {
                $headers = array_change_key_case(array: $headers, case: CASE_LOWER);
                $headers = $headers['authorization'] ?? '';
            }

            $headers = trim(string: $headers);

            if ($headers && preg_match(pattern: '/Bearer\s(\S+)/', subject: $headers, matches: $matches)) {
                return $matches[1];
            }

            return null;
        }
    }