<?php

declare(strict_types=1);

namespace App\Util;

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

/**
 * Class JWTManager
 * Manages generation and validation of JSON Web Tokens (JWT).
 */
class JWTManager
{
    private string $secretKey;
    private string $algorithm = 'HS256';

    /**
     * JWTManager constructor.
     * @param string $secretKey The secret key used for signing the JWT.
     */
    public function __construct(string $secretKey = \JWT_TOKEN)
    {
        $this->secretKey = $secretKey;
    }

    /**
     * Generates a JWT token.
     * @param array $data The data to be encoded in the JWT payload.
     * @return string The generated JWT token.
     */
    public function generateToken(array $data): string
    {
        $payload = [
            'iss' => \URLROOT, // Issuer of the token
            'aud' => \URLROOT, // Audience of the token
            'iat' => time(),  // Issued at
            'nbf' => time(),  // Not before
            'exp' => time() + JWT_EXPIRE, // Expiration time
            'data' => $data, // Additional data
        ];

        return JWT::encode($payload, $this->secretKey, $this->algorithm);
    }

    /**
     * Refreshes an expired token.
     * @param string $token The expired JWT token.
     * @return string|null The new JWT token or null if refresh is not possible.
     */
    public function refreshToken(string $token): ?string
    {
        try {
            // Decode the token without verifying expiration
            $payload = JWT::decode($token, new Key($this->secretKey, $this->algorithm));

            // Check if the token is too old to refresh (e.g., 7 days)
            $maxRefreshTime = JWT_EXPIRE; // 7 days in seconds
            if (time() - $payload->iat > $maxRefreshTime) {
                return null;
            }

            // Generate a new token with updated expiration time
            $newPayload = [
                'iss' => $payload->iss,
                'aud' => $payload->aud,
                'iat' => time(),
                'nbf' => time(),
                'exp' => time() + JWT_EXPIRE,
                'data' => $payload->data,
            ];

            $newToken =  JWT::encode($newPayload, $this->secretKey, $this->algorithm);
            return $newToken;
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * Checks if a JWT token is valid.
     * @param string $token The JWT token to validate.
     * @return bool True if the token is valid, false otherwise.
     */
    public function isTokenValid(string $token): bool
    {
        try {
            $data = $this->decodeToken($token);

            if ($data->iss !== \URLROOT) {
                return false;
            }

            if ($data->aud !== \URLROOT) {
                return false;
            }

            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Checks if a JWT token is expired.
     * @param string $token The JWT token to check.
     * @return bool True if the token is expired, false otherwise.
     */
    public function isTokenExpired(string $token): bool
    {
        $data = $this->decodeToken($token, false);
        return $data ? $data->exp < time() : true;
    }

    /**
     * Retrieves the data from a JWT token.
     * @param string $token The JWT token.
     * @return array The data stored in the token.
     */
    public function getTokenData(string $token): ?array
    {
        $data = $this->decodeToken($token, false);
        return $data ? (array) $data->data : null;
    }

    /**
     * Decodes a JWT token.
     * @param string $token The JWT token to decode.
     * @return object The decoded token.
     */
    public function decodeToken(string $token)
    {
        $data = JWT::decode($token, new Key($this->secretKey, $this->algorithm));
        return $data;
    }

    private function base64UrlEncode($data)
    {
        $base64 = base64_encode($data);
        $base64Url = strtr($base64, '+/', '-_');
        return rtrim($base64Url, '=');
    }

    private function base64UrlDecode($data)
    {
        $base64 = strtr($data, '-_', '+/');
        $base64Padded = str_pad($base64, strlen($base64) % 4, '=', STR_PAD_RIGHT);
        return base64_decode($base64Padded);
    }
}
