<?php

declare(strict_types=1);

namespace App\Framework\Notification\Channels\Telegram;

use App\Framework\Http\Method;
use App\Framework\HttpClient\ClientRequest;
use App\Framework\HttpClient\HttpClient;
use App\Framework\Notification\Channels\Telegram\ValueObjects\{TelegramChatId, TelegramMessageId, InlineKeyboard};
use App\Framework\Notification\Channels\Telegram\Webhook\{CallbackResponse, TelegramCallbackQuery};

/**
 * Telegram Bot API client
 *
 * Handles communication with Telegram Bot API using framework's HttpClient
 */
final readonly class TelegramClient
{
    public function __construct(
        private HttpClient $httpClient,
        private TelegramConfig $config
    ) {
    }

    /**
     * Send a text message
     *
     * @param TelegramChatId $chatId Recipient chat ID
     * @param string $text Message text (1-4096 characters)
     * @param string|null $parseMode Message format (Markdown, MarkdownV2, HTML)
     * @param InlineKeyboard|null $keyboard Inline keyboard with action buttons
     */
    public function sendMessage(
        TelegramChatId $chatId,
        string $text,
        ?string $parseMode = null,
        ?InlineKeyboard $keyboard = null
    ): TelegramResponse {
        $payload = [
            'chat_id' => $chatId->toString(),
            'text' => $text,
        ];

        if ($parseMode !== null) {
            $payload['parse_mode'] = $parseMode;
        }

        if ($keyboard !== null) {
            $payload['reply_markup'] = $keyboard->toArray();
        }

        return $this->sendRequest($this->config->getSendMessageEndpoint(), $payload);
    }

    /**
     * Get bot information
     */
    public function getMe(): array
    {
        $request = ClientRequest::json(
            method: Method::POST,
            url: $this->config->getGetMeEndpoint(),
            data: []
        );

        $response = $this->httpClient->send($request);

        if (!$response->isSuccessful()) {
            throw new TelegramApiException(
                "Failed to get bot info: HTTP {$response->status->value}",
                $response->status->value
            );
        }

        $data = $response->json();

        if (!isset($data['ok']) || $data['ok'] !== true) {
            $errorMessage = $data['description'] ?? 'Unknown error';
            throw new TelegramApiException(
                "Telegram API error: {$errorMessage}",
                $data['error_code'] ?? 0
            );
        }

        return $data['result'];
    }

    /**
     * Set webhook URL for receiving updates
     *
     * @param string $url HTTPS URL to receive webhook updates
     * @param string|null $secretToken Secret token for webhook verification
     * @param array $allowedUpdates List of update types to receive
     */
    public function setWebhook(
        string $url,
        ?string $secretToken = null,
        array $allowedUpdates = []
    ): bool {
        $payload = ['url' => $url];

        if ($secretToken !== null) {
            $payload['secret_token'] = $secretToken;
        }

        if (!empty($allowedUpdates)) {
            $payload['allowed_updates'] = $allowedUpdates;
        }

        $request = ClientRequest::json(
            method: Method::POST,
            url: $this->config->getSetWebhookEndpoint(),
            data: $payload
        );

        $response = $this->httpClient->send($request);

        if (!$response->isSuccessful()) {
            throw new TelegramApiException(
                "Failed to set webhook: HTTP {$response->status->value}",
                $response->status->value
            );
        }

        $data = $response->json();

        if (!isset($data['ok']) || $data['ok'] !== true) {
            $errorMessage = $data['description'] ?? 'Unknown error';
            throw new TelegramApiException(
                "Telegram API error: {$errorMessage}",
                $data['error_code'] ?? 0
            );
        }

        return $data['result'] === true;
    }

    /**
     * Delete webhook and switch back to getUpdates polling
     */
    public function deleteWebhook(): bool
    {
        $request = ClientRequest::json(
            method: Method::POST,
            url: $this->config->getDeleteWebhookEndpoint(),
            data: []
        );

        $response = $this->httpClient->send($request);

        if (!$response->isSuccessful()) {
            throw new TelegramApiException(
                "Failed to delete webhook: HTTP {$response->status->value}",
                $response->status->value
            );
        }

        $data = $response->json();

        if (!isset($data['ok']) || $data['ok'] !== true) {
            $errorMessage = $data['description'] ?? 'Unknown error';
            throw new TelegramApiException(
                "Telegram API error: {$errorMessage}",
                $data['error_code'] ?? 0
            );
        }

        return $data['result'] === true;
    }

    /**
     * Answer callback query from inline keyboard button
     *
     * Must be called within 30 seconds after callback query is received
     *
     * @param string $callbackQueryId Unique identifier for the callback query
     * @param CallbackResponse $response Response to send to user
     */
    public function answerCallbackQuery(
        string $callbackQueryId,
        CallbackResponse $response
    ): bool {
        $payload = [
            'callback_query_id' => $callbackQueryId,
            'text' => $response->text,
            'show_alert' => $response->showAlert,
        ];

        $request = ClientRequest::json(
            method: Method::POST,
            url: $this->config->getAnswerCallbackQueryEndpoint(),
            data: $payload
        );

        $httpResponse = $this->httpClient->send($request);

        if (!$httpResponse->isSuccessful()) {
            throw new TelegramApiException(
                "Failed to answer callback query: HTTP {$httpResponse->status->value}",
                $httpResponse->status->value
            );
        }

        $data = $httpResponse->json();

        if (!isset($data['ok']) || $data['ok'] !== true) {
            $errorMessage = $data['description'] ?? 'Unknown error';
            throw new TelegramApiException(
                "Telegram API error: {$errorMessage}",
                $data['error_code'] ?? 0
            );
        }

        return $data['result'] === true;
    }

    /**
     * Edit message text
     *
     * @param TelegramChatId $chatId Chat containing the message
     * @param TelegramMessageId $messageId Message to edit
     * @param string $text New text
     * @param InlineKeyboard|null $keyboard Optional new keyboard
     */
    public function editMessageText(
        TelegramChatId $chatId,
        TelegramMessageId $messageId,
        string $text,
        ?InlineKeyboard $keyboard = null
    ): bool {
        $payload = [
            'chat_id' => $chatId->toString(),
            'message_id' => $messageId->value,
            'text' => $text,
        ];

        if ($keyboard !== null) {
            $payload['reply_markup'] = $keyboard->toArray();
        }

        $request = ClientRequest::json(
            method: Method::POST,
            url: $this->config->getEditMessageTextEndpoint(),
            data: $payload
        );

        $response = $this->httpClient->send($request);

        if (!$response->isSuccessful()) {
            throw new TelegramApiException(
                "Failed to edit message: HTTP {$response->status->value}",
                $response->status->value
            );
        }

        $data = $response->json();

        if (!isset($data['ok']) || $data['ok'] !== true) {
            $errorMessage = $data['description'] ?? 'Unknown error';
            throw new TelegramApiException(
                "Telegram API error: {$errorMessage}",
                $data['error_code'] ?? 0
            );
        }

        return true;
    }

    /**
     * Send photo
     *
     * @param TelegramChatId $chatId Recipient chat ID
     * @param string $photo File path or file_id
     * @param string|null $caption Photo caption (0-1024 characters)
     * @param string|null $parseMode Caption format (Markdown, MarkdownV2, HTML)
     */
    public function sendPhoto(
        TelegramChatId $chatId,
        string $photo,
        ?string $caption = null,
        ?string $parseMode = null
    ): TelegramResponse {
        $payload = [
            'chat_id' => $chatId->toString(),
            'photo' => $photo,
        ];

        if ($caption !== null) {
            $payload['caption'] = $caption;
        }

        if ($parseMode !== null) {
            $payload['parse_mode'] = $parseMode;
        }

        return $this->sendRequest($this->config->getSendPhotoEndpoint(), $payload);
    }

    /**
     * Send video
     *
     * @param TelegramChatId $chatId Recipient chat ID
     * @param string $video File path or file_id
     * @param string|null $caption Video caption (0-1024 characters)
     * @param string|null $parseMode Caption format (Markdown, MarkdownV2, HTML)
     * @param int|null $duration Video duration in seconds
     */
    public function sendVideo(
        TelegramChatId $chatId,
        string $video,
        ?string $caption = null,
        ?string $parseMode = null,
        ?int $duration = null
    ): TelegramResponse {
        $payload = [
            'chat_id' => $chatId->toString(),
            'video' => $video,
        ];

        if ($caption !== null) {
            $payload['caption'] = $caption;
        }

        if ($parseMode !== null) {
            $payload['parse_mode'] = $parseMode;
        }

        if ($duration !== null) {
            $payload['duration'] = $duration;
        }

        return $this->sendRequest($this->config->getSendVideoEndpoint(), $payload);
    }

    /**
     * Send audio
     *
     * @param TelegramChatId $chatId Recipient chat ID
     * @param string $audio File path or file_id
     * @param string|null $caption Audio caption (0-1024 characters)
     * @param string|null $parseMode Caption format (Markdown, MarkdownV2, HTML)
     * @param int|null $duration Audio duration in seconds
     */
    public function sendAudio(
        TelegramChatId $chatId,
        string $audio,
        ?string $caption = null,
        ?string $parseMode = null,
        ?int $duration = null
    ): TelegramResponse {
        $payload = [
            'chat_id' => $chatId->toString(),
            'audio' => $audio,
        ];

        if ($caption !== null) {
            $payload['caption'] = $caption;
        }

        if ($parseMode !== null) {
            $payload['parse_mode'] = $parseMode;
        }

        if ($duration !== null) {
            $payload['duration'] = $duration;
        }

        return $this->sendRequest($this->config->getSendAudioEndpoint(), $payload);
    }

    /**
     * Send document
     *
     * @param TelegramChatId $chatId Recipient chat ID
     * @param string $document File path or file_id
     * @param string|null $caption Document caption (0-1024 characters)
     * @param string|null $parseMode Caption format (Markdown, MarkdownV2, HTML)
     * @param string|null $filename Custom filename for the document
     */
    public function sendDocument(
        TelegramChatId $chatId,
        string $document,
        ?string $caption = null,
        ?string $parseMode = null,
        ?string $filename = null
    ): TelegramResponse {
        $payload = [
            'chat_id' => $chatId->toString(),
            'document' => $document,
        ];

        if ($caption !== null) {
            $payload['caption'] = $caption;
        }

        if ($parseMode !== null) {
            $payload['parse_mode'] = $parseMode;
        }

        if ($filename !== null) {
            $payload['filename'] = $filename;
        }

        return $this->sendRequest($this->config->getSendDocumentEndpoint(), $payload);
    }

    /**
     * Send location
     *
     * @param TelegramChatId $chatId Recipient chat ID
     * @param float $latitude Latitude of location
     * @param float $longitude Longitude of location
     */
    public function sendLocation(
        TelegramChatId $chatId,
        float $latitude,
        float $longitude
    ): TelegramResponse {
        $payload = [
            'chat_id' => $chatId->toString(),
            'latitude' => $latitude,
            'longitude' => $longitude,
        ];

        return $this->sendRequest($this->config->getSendLocationEndpoint(), $payload);
    }

    /**
     * Send request to Telegram API using HttpClient
     */
    private function sendRequest(string $endpoint, array $payload): TelegramResponse
    {
        // Create JSON request
        $request = ClientRequest::json(
            method: Method::POST,
            url: $endpoint,
            data: $payload
        );

        $response = $this->httpClient->send($request);

        if (!$response->isSuccessful()) {
            throw new TelegramApiException(
                "Telegram API request failed: HTTP {$response->status->value}",
                $response->status->value
            );
        }

        // Parse response
        $data = $response->json();

        if (!isset($data['ok']) || $data['ok'] !== true) {
            $errorMessage = $data['description'] ?? 'Unknown error';
            $errorCode = $data['error_code'] ?? 0;

            throw new TelegramApiException(
                "Telegram API error ({$errorCode}): {$errorMessage}",
                $errorCode
            );
        }

        // Extract message ID from response
        $messageId = $data['result']['message_id'] ?? null;

        if ($messageId === null) {
            throw new \RuntimeException('Telegram API response missing message ID');
        }

        return new TelegramResponse(
            success: true,
            messageId: TelegramMessageId::fromInt($messageId),
            rawResponse: $data
        );
    }
}
