<?php
declare(strict_types=1);

final class Telegram
{
    private const API_BASE = 'https://api.telegram.org/bot';
    private const MAX_ATTEMPTS = 5;
    private const BASE_DELAY_US = 350000; // 0.35s between Telegram calls in the same PHP request
    private static float $lastRequestAt = 0.0;

    public function __construct(private string $token) {}

    public static function isValidTokenFormat(string $token): bool
    {
        return (bool)preg_match('/^\d{6,12}:[A-Za-z0-9_\-]{30,}$/', trim($token));
    }

    public function api(string $method, array $params = []): array
    {
        if (!self::isValidTokenFormat($this->token)) {
            throw new RuntimeException('فرمت توکن تلگرام معتبر نیست.');
        }
        if (!function_exists('curl_init')) {
            throw new RuntimeException('افزونه curl روی هاست فعال نیست.');
        }

        $lastError = null;
        for ($attempt = 1; $attempt <= self::MAX_ATTEMPTS; $attempt++) {
            $this->throttle();
            $response = $this->rawApiRequest($method, $params);
            self::$lastRequestAt = microtime(true);

            if ($response['transport_error'] !== null) {
                $lastError = 'خطای اتصال به تلگرام: ' . $response['transport_error'];
                if ($attempt < self::MAX_ATTEMPTS) {
                    usleep($this->retryDelayMicroseconds($attempt, null));
                    continue;
                }
                throw new RuntimeException($lastError);
            }

            $data = $response['data'];
            $httpCode = (int)$response['http_code'];
            if (!is_array($data)) {
                $lastError = 'پاسخ تلگرام قابل خواندن نیست. HTTP ' . $httpCode;
                if ($attempt < self::MAX_ATTEMPTS && ($httpCode === 429 || $httpCode >= 500 || $httpCode === 0)) {
                    usleep($this->retryDelayMicroseconds($attempt, null));
                    continue;
                }
                throw new RuntimeException($lastError);
            }

            if (($data['ok'] ?? false) === true) {
                return $data;
            }

            $errorCode = (int)($data['error_code'] ?? $httpCode);
            $desc = (string)($data['description'] ?? 'Telegram API error');
            $retryAfter = $this->extractRetryAfter($data, $desc);
            $lastError = $desc;

            // Telegram flood control. Do not fail immediately; wait and retry.
            if (($errorCode === 429 || $httpCode === 429) && $attempt < self::MAX_ATTEMPTS) {
                usleep($this->retryDelayMicroseconds($attempt, $retryAfter));
                continue;
            }

            // Transient Telegram/API gateway errors.
            if (($httpCode >= 500 || in_array($errorCode, [500, 502, 503, 504], true)) && $attempt < self::MAX_ATTEMPTS) {
                usleep($this->retryDelayMicroseconds($attempt, null));
                continue;
            }

            if ($errorCode === 429 || stripos($desc, 'Too Many Requests') !== false) {
                throw new RuntimeException('تلگرام موقتاً محدودیت درخواست داده است. چند ثانیه صبر کن و دوباره امتحان کن. متن خطا: ' . $desc);
            }
            throw new RuntimeException($desc);
        }

        throw new RuntimeException($lastError ?: 'خطای نامشخص تلگرام.');
    }

    private function rawApiRequest(string $method, array $params): array
    {
        $ch = curl_init(self::API_BASE . $this->token . '/' . $method);
        if ($ch === false) {
            return ['http_code' => 0, 'data' => null, 'transport_error' => 'curl_init failed'];
        }
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Expect:'],
            CURLOPT_POSTFIELDS => json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            CURLOPT_CONNECTTIMEOUT => 15,
            CURLOPT_TIMEOUT => 45,
            CURLOPT_ENCODING => '',
            CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
        ]);
        $body = curl_exec($ch);
        $error = curl_error($ch);
        $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($body === false) {
            return ['http_code' => $code, 'data' => null, 'transport_error' => ($error ?: 'unknown curl error')];
        }
        $decoded = json_decode((string)$body, true);
        return ['http_code' => $code, 'data' => is_array($decoded) ? $decoded : null, 'transport_error' => null];
    }

    private function throttle(): void
    {
        $elapsed = microtime(true) - self::$lastRequestAt;
        $need = self::BASE_DELAY_US / 1000000;
        if ($elapsed > 0 && $elapsed < $need) {
            usleep((int)(($need - $elapsed) * 1000000));
        }
    }

    private function extractRetryAfter(array $data, string $description): ?int
    {
        if (isset($data['parameters']['retry_after']) && is_numeric($data['parameters']['retry_after'])) {
            return max(1, (int)$data['parameters']['retry_after']);
        }
        if (preg_match('/retry after\s+(\d+)/i', $description, $m)) {
            return max(1, (int)$m[1]);
        }
        return null;
    }

    private function retryDelayMicroseconds(int $attempt, ?int $retryAfter): int
    {
        if ($retryAfter !== null) {
            return (int)(min(30, $retryAfter + 1) * 1000000);
        }
        $seconds = min(8, $attempt); // 1s, 2s, 3s... max 8s
        return (int)($seconds * 1000000);
    }

    public function getMe(): array
    {
        return $this->api('getMe')['result'];
    }

    public function sendMessage(int|string $chatId, string $text, ?array $replyMarkup = null): array
    {
        $params = [
            'chat_id' => $chatId,
            'text' => $text,
            'parse_mode' => 'HTML',
            'disable_web_page_preview' => true,
        ];
        if ($replyMarkup) {
            $params['reply_markup'] = $replyMarkup;
        }
        return $this->api('sendMessage', $params);
    }

    public function sendPhoto(int|string $chatId, string $photo, string $caption = '', ?array $replyMarkup = null): array
    {
        $params = [
            'chat_id' => $chatId,
            'photo' => $photo,
            'caption' => $caption,
            'parse_mode' => 'HTML',
        ];
        if ($replyMarkup) $params['reply_markup'] = $replyMarkup;
        return $this->api('sendPhoto', $params);
    }

    public function sendDocument(int|string $chatId, string $document, string $caption = '', ?array $replyMarkup = null): array
    {
        $params = [
            'chat_id' => $chatId,
            'document' => $document,
            'caption' => $caption,
            'parse_mode' => 'HTML',
        ];
        if ($replyMarkup) $params['reply_markup'] = $replyMarkup;
        return $this->api('sendDocument', $params);
    }


    public function forwardMessage(int|string $chatId, int|string $fromChatId, int $messageId): array
    {
        return $this->api('forwardMessage', [
            'chat_id' => $chatId,
            'from_chat_id' => $fromChatId,
            'message_id' => $messageId,
        ]);
    }

    public function pinChatMessage(int|string $chatId, int $messageId, bool $disableNotification = true): array
    {
        return $this->api('pinChatMessage', [
            'chat_id' => $chatId,
            'message_id' => $messageId,
            'disable_notification' => $disableNotification,
        ]);
    }

    public function editMessageText(int|string $chatId, int $messageId, string $text, ?array $replyMarkup = null): array
    {
        $params = [
            'chat_id' => $chatId,
            'message_id' => $messageId,
            'text' => $text,
            'parse_mode' => 'HTML',
            'disable_web_page_preview' => true,
        ];
        if ($replyMarkup) $params['reply_markup'] = $replyMarkup;
        return $this->api('editMessageText', $params);
    }

    public function answerCallbackQuery(string $id, string $text = '', bool $showAlert = false): array
    {
        return $this->api('answerCallbackQuery', ['callback_query_id' => $id, 'text' => $text, 'show_alert' => $showAlert]);
    }

    public function getChatMember(int|string $chatId, int|string $userId): array
    {
        return $this->api('getChatMember', ['chat_id' => $chatId, 'user_id' => $userId])['result'];
    }

    public function setWebhook(string $url, ?string $secretToken = null, ?array $allowedUpdates = null): array
    {
        $params = [
            'url' => $url,
            'drop_pending_updates' => true,
            'allowed_updates' => $allowedUpdates ?: ['message', 'callback_query'],
            'max_connections' => 40,
        ];
        if ($secretToken) {
            $params['secret_token'] = $secretToken;
        }
        return $this->api('setWebhook', $params);
    }

    public function deleteWebhook(): array
    {
        return $this->api('deleteWebhook', ['drop_pending_updates' => true]);
    }

    public function setMenuButton(string $url, string $text = 'پنل') : array
    {
        return $this->api('setChatMenuButton', [
            'menu_button' => [
                'type' => 'web_app',
                'text' => $text,
                'web_app' => ['url' => $url],
            ],
        ]);
    }
}
