<?php

namespace Openguru\OpenCartFramework\Telegram;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\Enums\ChatAction;
use Openguru\OpenCartFramework\Telegram\Exceptions\DecodeTelegramInitDataException;
use Openguru\OpenCartFramework\Telegram\Exceptions\TelegramClientException;
use Psr\Log\LoggerInterface;

class TelegramService
{
    private ?string $botToken;
    private TelegramInitDataDecoder $initDataDecoder;
    private LoggerInterface $logger;

    public function __construct(
        TelegramInitDataDecoder $initDataDecoder,
        LoggerInterface $logger,
        ?string $botToken = null
    ) {
        $this->botToken = $botToken;
        $this->initDataDecoder = $initDataDecoder;
        $this->logger = $logger;
    }

    public function prepareMessage(string $template, array $variables = []): string
    {
        return str_replace(array_keys($variables), array_values($variables), $template);
    }

    /**
     * @throws TelegramClientException
     * @throws GuzzleException
     * @throws \JsonException
     */
    public function sendMessage(
        int $chatId,
        string $text,
        array $replyMarkup = [],
        string $chatAction = ChatAction::TYPING,
        string $parseMode = 'html'
    ): void {
        if (! $this->botToken) {
            return;
        }

        $this->sendChatAction($chatId, $chatAction);

        $params = [
            'chat_id' => $chatId,
            'text' => $text,
            'parse_mode' => $parseMode,
        ];

        if ($replyMarkup) {
            $params['reply_markup'] = json_encode($replyMarkup, JSON_THROW_ON_ERROR);
        }

        $this->exec('sendMessage', $params);
    }

    private function createGuzzleClient(string $uri): Client
    {
        return new Client([
            'base_uri' => $uri,
            'timeout' => 5.0,
        ]);
    }

    public function setBotToken(string $botToken): TelegramService
    {
        $this->botToken = $botToken;

        return $this;
    }

    /**
     * Command used to exec telegram api methods
     * @see https://core.telegram.org/bots/api
     *
     * @throws GuzzleException
     * @throws TelegramClientException
     * @throws \JsonException
     */
    public function exec(string $methodName, array $params = []): array
    {
        if (! $this->botToken) {
            throw new TelegramClientException('BotToken is empty');
        }

        $client = $this->createGuzzleClient("https://api.telegram.org/bot{$this->botToken}/");

        try {
            $response = $client->post($methodName, [
                'json' => $params,
            ]);

            $json = json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);

            if (! $json['ok']) {
                throw new TelegramClientException(
                    $json['code'] ?? 'Unknown Code' . ': ' . $json['description'] ?? 'Unknown Error'
                );
            }

            return $json;
        } catch (ClientException $exception) {
            $response = $exception->getResponse()->getBody()->getContents();
            $decoded = json_decode($response, true, 512, JSON_THROW_ON_ERROR);

            throw new TelegramClientException(
                $decoded['description'] ?? $exception->getMessage(),
                $decoded['code'] ?? $exception->getCode(),
            );
        }
    }

    public function getWebhookUrl(): string
    {
        $webhookInfo = $this->exec('getWebhookInfo');

        return Arr::get($webhookInfo, 'result.url');
    }

    public function sendChatAction(int $chatId, string $action): void
    {
        $this->exec('sendChatAction', [
            'chat_id' => $chatId,
            'action' => $action,
        ]);
    }

    public function escapeTgSpecialCharacters(string $text): string
    {
        // Набор спецсимволов для MarkdownV2
        $specials = '_*[]()~`>#+-=|{}.!';

        $out = '';
        $len = strlen($text);          // работаем побайтно: спецсимволы — ASCII
        $prevWasBackslash = false;

        for ($i = 0; $i < $len; $i++) {
            $ch = $text[$i];

            if ($prevWasBackslash) {
                // Предыдущий был "\", этот символ считаем уже экранированным — просто добавим как есть
                $out .= $ch;
                $prevWasBackslash = false;
                continue;
            }

            if ($ch === '\\') {
                // Запоминаем, что встретили слеш; пока не знаем, что дальше
                $out .= '\\';
                $prevWasBackslash = true;
                continue;
            }

            // Если текущий символ — спецсимвол и он НЕ экранирован (мы уже знаем, что предыдущий не "\")
            if (strpos($specials, $ch) !== false) {
                $out .= '\\' . $ch;
            } else {
                $out .= $ch;
            }
        }

        // Если строка закончилась на одиночный "\" — довэкранируем его
        if ($prevWasBackslash) {
            $out .= '\\';
        }

        return $out;
    }

    public function getChat(int $chatId): array
    {
        $response = $this->exec('getChat', [
            'chat_id' => $chatId,
        ]);

        return $response['result'];
    }

    public function getMe(): array
    {
        $response = $this->exec('getMe');

        return $response['result'];
    }

    public function userId(?string $initDataRaw = null): ?int
    {
        if (! $initDataRaw) {
            return null;
        }

        try {
            $decoded = $this->initDataDecoder->decode($initDataRaw);

            return Arr::get($decoded, 'user.id');
        } catch (DecodeTelegramInitDataException $e) {
            $this->logger->error($e->getMessage(), ['exception' => $e]);
        }

        return null;
    }

    public function getMiniAppUrl(): string
    {
        $me = $this->getMe();

        return sprintf('https://t.me/%s?startapp', $me['username']);
    }
}
