<?php
namespace mibazarow\pushsender;

class FirebaseToken
{

    /**
     * Статический метод для получения токена доступа Firebase
     * @param string $jsonInput - путь к файлу или JSON-строка
     * @param int|null $AccessTokenMaximumExpirationTime - максимально допустимое время жизни токена (секунд), по умолчанию 3600
     * @param int|null $RequestTimeout - таймаут запроса curl (секунд), по умолчанию 10
     * @return string - токен доступа
     * @throws \Exception
     */
    public static function getAccessToken(
        string $jsonInput,
        ?int $AccessTokenMaximumExpirationTime = 3600,
        ?int $RequestTimeout = 10): string
    {
        // Проверяем, существует ли файл по пути $jsonInput
        if (file_exists($jsonInput)) {
            // Если файл есть, читаем его содержимое
            $content = file_get_contents($jsonInput);
            // Декодируем содержимое JSON в массив
            $data = json_decode($content, true);
        } else {
            // Иначе предполагаем, что входная строка — JSON
            $data = json_decode($jsonInput, true);
        }

        if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
            throw new \Exception('Ошибка при декодировании JSON: ' . json_last_error_msg());
        }

        // Проверяем наличие обязательных полей: private_key и client_email
        if (!isset($data['private_key']) || !isset($data['client_email'])) {
            throw new \Exception('Недостающие поля в JSON: private_key или client_email');
        }

        // Получаем токен доступа
        return self::requestAccessToken($data['private_key'], $data['client_email'], $AccessTokenMaximumExpirationTime, $RequestTimeout);
    }


    public static function requestAccessToken(
        string $PrivateKey,
        string $AccountName,
        ?int   $AccessTokenMaximumExpirationTime = 3600,
        ?int   $RequestTimeout = 10
    ): string
    {
        // Проверка наличия приватного ключа и имени аккаунта
        if ($PrivateKey !== '' && $AccountName !== '') {
            // Время начала выполнения запроса
            $StartTime = time();

            // Создаем заголовок JWT (алгоритм RS256, тип JWT)
            $Header = json_encode([
                'alg' => 'RS256',
                'typ' => 'JWT'
            ]);

            // Проверка и установка значения таймаута
            if (!isset($RequestTimeout) || $RequestTimeout < 0) $RequestTimeout = 10;

            // Ограничение времени жизни токена, максимум 3600 секунд
            if (!isset($AccessTokenMaximumExpirationTime) || $AccessTokenMaximumExpirationTime <= 0 || $AccessTokenMaximumExpirationTime > 3600) {
                $AccessTokenMaximumExpirationTime = 3600;
            }

            // Рассчет времени окончания действия токена
            $AccessTokenExpirationEndTime = $StartTime + $AccessTokenMaximumExpirationTime;

            // URL аудитории (Firebase OAuth2 endpoint)
            $Aud = 'https://oauth2.googleapis.com/token';

            // Создаем тело JWT (заявку) с нужными данными
            $Data = json_encode([
                'iss' => $AccountName, // issuer — email аккаунта
                'scope' => 'https://www.googleapis.com/auth/firebase.messaging', // область
                'aud' => $Aud, // аудитория
                'exp' => $AccessTokenExpirationEndTime, // время истечения
                'iat' => $StartTime // время выпуска
            ]);

            // Кодируем заголовок и данные в base64url
            $Base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($Header));
            $Base64UrlData = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($Data));

            // Подписываем данные приватным ключом
            $Signature = '';
            $PrivateKeyResource = openssl_get_privatekey($PrivateKey);
            openssl_sign("{$Base64UrlHeader}.{$Base64UrlData}", $Signature, $PrivateKeyResource, OPENSSL_ALGO_SHA256);
            // Кодируем подпись в base64url
            $Base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($Signature));

            // Формируем JWT
            $JWT = "{$Base64UrlHeader}.{$Base64UrlData}.{$Base64UrlSignature}";

            // Подготовка данных для POST-запроса
            $PostFields = [
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion' => $JWT
            ];

            // Инициализация curl-запроса
            $Ch = curl_init($Aud);
            curl_setopt($Ch, CURLOPT_CONNECTTIMEOUT, $RequestTimeout);
            curl_setopt($Ch, CURLOPT_TIMEOUT, $RequestTimeout);
            curl_setopt($Ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($Ch, CURLOPT_POSTFIELDS, http_build_query($PostFields));

            // Выполняем запрос и получаем ответ
            $Result = curl_exec($Ch);
            $Errno = 0;
            $Error = '';

            if ($Result === false) {
                // В случае ошибки curl сохраняем ошибку
                $Errno = curl_errno($Ch);
                $Error = curl_error($Ch);
            }

            // Закрываем curl-сессию
            curl_close($Ch);

                // Обработка ошибок curl
            if ($Errno > 0 && $Error != '') {
                throw new \Exception($Error, $Errno);
            } else {
                if ($Result !== false) {
                    // Декодируем ответ JSON
                    $ResultArray = @json_decode($Result, true);
                    // Проверяем наличие access_token
                    if (is_array($ResultArray)) {
                        if (isset($ResultArray['access_token'])) {
                            // Возвращаем полученный токен
                            return trim($ResultArray['access_token']);
                        } else if (isset($ResultArray['error'])) {
                            // Обрабатываем ошибку из API
                            throw new \Exception($ResultArray['error']['message'], $ResultArray['error']['code']);
                        }
                    }
                }
            }
        } else {
            // Проверяем наличие обязательных данных и выбрасываем исключения
            if ($PrivateKey == '') {
                throw new \Exception('Не указан закрытый ключ.', 110);
            }
            if ($AccountName == '') {
                throw new \Exception('Не указано имя учетной записи.', 110);
            }
        }
        // Возвращаем пустую строку, если что-то пошло не так
        return '';
    }
}