<?php
if (!defined('ABSPATH')) exit;

/**
 * Telegram Bot API integration for groups, supergroups, channels and forum topics.
 */
class Calliope_SP_Telegram {
    private $api_base = 'https://api.telegram.org';

    /**
     * Normalize a Telegram chat destination.
     * Accepts @publicchannel, numeric IDs like -1001234567890, and public t.me links.
     *
     * @param string $chat_id Raw chat id/username/link.
     * @return string
     */
    public function normalize_chat_id($chat_id) {
        $chat_id = trim((string) $chat_id);
        if ($chat_id === '') {
            return '';
        }

        if (preg_match('#^https?://(?:www\.)?t\.me/(?:s/)?([A-Za-z0-9_]{5,})/?$#i', $chat_id, $matches)) {
            $chat_id = '@' . $matches[1];
        }

        if (preg_match('/^@[A-Za-z0-9_]{5,}$/', $chat_id)) {
            return sanitize_text_field($chat_id);
        }

        if (preg_match('/^-?\d+$/', $chat_id)) {
            return sanitize_text_field($chat_id);
        }

        // Keep a conservative sanitized value so Telegram can return a clear API error.
        return sanitize_text_field($chat_id);
    }

    /**
     * @return string
     */
    private function get_bot_token() {
        return trim((string) get_option('calliope_sp_telegram_bot_token', ''));
    }

    /**
     * @return string
     */
    private function get_chat_id() {
        return $this->normalize_chat_id((string) get_option('calliope_sp_telegram_chat_id', ''));
    }

    /**
     * @return int
     */
    private function get_message_thread_id() {
        return absint(get_option('calliope_sp_telegram_message_thread_id', 0));
    }

    /**
     * @param string $method Telegram API method.
     * @param array<string,mixed> $params Request params.
     * @return array<string,mixed>|WP_Error
     */
    private function request($method, array $params = []) {
        $token = $this->get_bot_token();
        if ($token === '') {
            return new WP_Error('telegram_missing_token', __('Missing Telegram Bot Token.', 'calliope-social-publisher'));
        }

        $method = preg_replace('/[^A-Za-z0-9_]/', '', (string) $method);
        if ($method === '') {
            return new WP_Error('telegram_invalid_method', __('Invalid Telegram API method.', 'calliope-social-publisher'));
        }

        $safe_token = preg_replace('/[^A-Za-z0-9:_-]/', '', $token);
        if ($safe_token === '') {
            return new WP_Error('telegram_invalid_token', __('Invalid Telegram Bot Token.', 'calliope-social-publisher'));
        }

        $url = trailingslashit($this->api_base) . 'bot' . $safe_token . '/' . $method;

        $response = wp_remote_post($url, [
            'headers' => [
                'Accept'       => 'application/json',
                'Content-Type' => 'application/json; charset=utf-8',
            ],
            'body'    => wp_json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            'timeout' => 45,
        ]);

        if (is_wp_error($response)) {
            return $response;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $raw  = (string) wp_remote_retrieve_body($response);
        $json = json_decode($raw, true);

        if ($code < 200 || $code >= 300 || !is_array($json) || empty($json['ok'])) {
            $message = $this->extract_api_error($json, $raw, $code);
            return new WP_Error('telegram_api_error', $message, [
                'http_code' => $code,
                'method'    => $method,
            ]);
        }

        return $json;
    }

    /**
     * Send a Telegram API request as multipart/form-data so photos are uploaded
     * directly from this WordPress server instead of asking Telegram to fetch a
     * public URL. This avoids host/CDN/TLS issues with wp-content or wp-json URLs.
     *
     * @param string $method Telegram API method.
     * @param array<string,mixed> $params Request params.
     * @param string $file_field Multipart field name.
     * @param string $file_path Local absolute file path.
     * @param string $mime File MIME type.
     * @return array<string,mixed>|WP_Error
     */
    private function request_multipart($method, array $params = [], $file_field = '', $file_path = '', $mime = 'image/jpeg') {
        $token = $this->get_bot_token();
        if ($token === '') {
            return new WP_Error('telegram_missing_token', __('Missing Telegram Bot Token.', 'calliope-social-publisher'));
        }

        $method = preg_replace('/[^A-Za-z0-9_]/', '', (string) $method);
        if ($method === '') {
            return new WP_Error('telegram_invalid_method', __('Invalid Telegram API method.', 'calliope-social-publisher'));
        }

        $safe_token = preg_replace('/[^A-Za-z0-9:_-]/', '', $token);
        if ($safe_token === '') {
            return new WP_Error('telegram_invalid_token', __('Invalid Telegram Bot Token.', 'calliope-social-publisher'));
        }

        $file_field = sanitize_key((string) $file_field);
        $file_path = (string) $file_path;
        if ($file_field === '' || $file_path === '' || !file_exists($file_path) || !is_file($file_path) || !is_readable($file_path)) {
            return new WP_Error('telegram_file_upload_failed', __('Telegram could not read the generated image file.', 'calliope-social-publisher'));
        }

        if (!function_exists('curl_init') || !function_exists('curl_file_create')) {
            // Fallback for rare PHP builds without cURL file upload support.
            $params[$file_field] = isset($params[$file_field]) ? $params[$file_field] : '';
            return $this->request($method, $params);
        }

        $url = trailingslashit($this->api_base) . 'bot' . $safe_token . '/' . $method;
        $post_fields = [];
        foreach ($params as $key => $value) {
            if ($key === $file_field) {
                continue;
            }
            if (is_bool($value)) {
                $post_fields[$key] = $value ? '1' : '0';
            } elseif (is_scalar($value)) {
                $post_fields[$key] = (string) $value;
            }
        }

        $post_fields[$file_field] = curl_file_create($file_path, (string) $mime, basename($file_path));

        $ch = curl_init($url);
        if (!$ch) {
            return new WP_Error('telegram_file_upload_failed', __('Telegram could not start the file upload request.', 'calliope-social-publisher'));
        }

        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json']);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);

        $raw = curl_exec($ch);
        $curl_error = curl_error($ch);
        $code = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);

        if ($raw === false) {
            return new WP_Error('telegram_api_error', $curl_error !== '' ? $curl_error : __('Telegram file upload failed.', 'calliope-social-publisher'), [
                'http_code' => $code,
                'method'    => $method,
            ]);
        }

        $raw = (string) $raw;
        $json = json_decode($raw, true);
        if ($code < 200 || $code >= 300 || !is_array($json) || empty($json['ok'])) {
            $message = $this->extract_api_error($json, $raw, $code);
            return new WP_Error('telegram_api_error', $message, [
                'http_code' => $code,
                'method'    => $method,
            ]);
        }

        return $json;
    }

    /**
     * @param mixed  $json Decoded response.
     * @param string $raw Raw response body.
     * @param int    $code HTTP status.
     * @return string
     */
    private function extract_api_error($json, $raw, $code) {
        if (is_array($json) && !empty($json['description'])) {
            return sprintf(
                /* translators: 1: HTTP status code, 2: Telegram API error message. */
                __('Telegram error (%1$d): %2$s', 'calliope-social-publisher'),
                (int) $code,
                sanitize_text_field((string) $json['description'])
            );
        }

        $raw = trim(wp_strip_all_tags((string) $raw));
        if ($raw !== '') {
            return sprintf(
                /* translators: 1: HTTP status code, 2: raw API response. */
                __('Telegram error (%1$d): %2$s', 'calliope-social-publisher'),
                (int) $code,
                sanitize_text_field($raw)
            );
        }

        return sprintf(
            /* translators: %d: HTTP status code. */
            __('Telegram error (%d): the API request failed.', 'calliope-social-publisher'),
            (int) $code
        );
    }

    /**
     * Validate token and destination without sending a public message.
     *
     * @return true|WP_Error
     */
    public function test_connection() {
        $chat_id = $this->get_chat_id();
        if ($this->get_bot_token() === '' || $chat_id === '') {
            return new WP_Error('telegram_missing_configuration', __('Missing Telegram Bot Token or Group/Channel ID.', 'calliope-social-publisher'));
        }

        $bot = $this->request('getMe');
        if (is_wp_error($bot)) {
            return $bot;
        }

        $chat = $this->request('getChat', [
            'chat_id' => $chat_id,
        ]);
        if (is_wp_error($chat)) {
            return $chat;
        }

        return true;
    }

    /**
     * Publish a post to Telegram.
     *
     * @param array<string,mixed> $data Normalized publish payload.
     * @return true|WP_Error
     */
    public function publish($data) {
        $chat_id = $this->get_chat_id();
        if ($this->get_bot_token() === '' || $chat_id === '') {
            return new WP_Error('telegram_missing_configuration', __('Missing Telegram configuration.', 'calliope-social-publisher'));
        }

        $message = $this->format_message((array) $data, 3900);
        $thread_id = $this->get_message_thread_id();
        $has_image = !empty($data['image_b64']);

        if ($message === '' && !$has_image) {
            return new WP_Error('telegram_empty_message', __('Telegram needs text, a link, hashtags, or an image to publish.', 'calliope-social-publisher'));
        }

        if ($has_image) {
            $image_asset = $this->image_url_from_b64((string) $data['image_b64']);
            if (is_wp_error($image_asset)) {
                return $image_asset;
            }

            $params = [
                'chat_id' => $chat_id,
            ];

            if ($message !== '') {
                $params['caption'] = $this->truncate_text($message, 1000);
            }

            if ($thread_id > 0) {
                $params['message_thread_id'] = $thread_id;
            }

            $result = $this->request_multipart(
                'sendPhoto',
                $params,
                'photo',
                (string) $image_asset['path'],
                (string) ($image_asset['mime'] ?? 'image/jpeg')
            );
            $this->delete_temp_image((string) $image_asset['path']);

            return is_wp_error($result) ? $result : true;
        }

        $params = [
            'chat_id' => $chat_id,
            'text'    => $this->truncate_text($message, 3900),
        ];

        if ($thread_id > 0) {
            $params['message_thread_id'] = $thread_id;
        }

        $result = $this->request('sendMessage', $params);
        return is_wp_error($result) ? $result : true;
    }

    /**
     * @param string $b64 Data URL or raw base64 image.
     * @return array{url:string,path:string}|WP_Error
     */
    private function image_url_from_b64($b64) {
        $asset = class_exists('Calliope_SP_Media_Helper')
            ? Calliope_SP_Media_Helper::create_public_image_from_b64($b64, [
                'context'         => 'telegram',
                'filename_prefix' => 'calliope-social-publisher-telegram',
                'preferred_mime'  => 'image/jpeg',
                'network'         => 'telegram',
                'max_attempts'    => 1,
                'url_strategy'    => 'static_upload',
                'skip_public_validation' => true,
            ])
            : new WP_Error('telegram_image_prepare_failed', __('Telegram could not prepare a public image URL.', 'calliope-social-publisher'));

        if (is_wp_error($asset)) {
            return $asset;
        }

        return [
            'url'  => (string) $asset['url'],
            'path' => (string) $asset['local_path'],
            'mime' => (string) ($asset['mime'] ?? 'image/jpeg'),
        ];
    }

    /**
     * @param string $path Temporary file path.
     * @return void
     */
    private function delete_temp_image($path) {
        $path = (string) $path;
        if ($path !== '' && file_exists($path) && is_file($path)) {
            wp_delete_file($path);
        }
    }

    /**
     * @param array<string,mixed> $data Payload.
     * @param int                 $max_length Max length.
     * @return string
     */
    private function format_message(array $data, $max_length = 3900) {
        $parts = [];

        $title = trim(wp_strip_all_tags((string) ($data['title'] ?? '')));
        $content = trim(wp_strip_all_tags((string) ($data['content'] ?? '')));
        $link = esc_url_raw((string) ($data['link'] ?? ''));
        $hashtags = trim(wp_strip_all_tags((string) ($data['hashtags'] ?? '')));

        if ($title !== '') {
            $parts[] = $title;
        }

        if ($content !== '') {
            $parts[] = $content;
        }

        if ($link !== '') {
            $parts[] = $link;
        }

        if ($hashtags !== '') {
            $parts[] = $hashtags;
        }

        $message = trim(preg_replace("/\n{3,}/", "\n\n", implode("\n\n", $parts)));
        return $this->truncate_text($message, (int) $max_length);
    }

    /**
     * @param string $text Text.
     * @param int    $max_length Max chars.
     * @return string
     */
    private function truncate_text($text, $max_length) {
        $text = trim((string) $text);
        $max_length = max(1, (int) $max_length);

        $length = function_exists('mb_strlen') ? mb_strlen($text, 'UTF-8') : strlen($text);
        if ($length <= $max_length) {
            return $text;
        }

        $suffix = '…';
        $cut_at = max(1, $max_length - 1);
        $slice = function_exists('mb_substr') ? mb_substr($text, 0, $cut_at, 'UTF-8') : substr($text, 0, $cut_at);

        return rtrim((string) $slice) . $suffix;
    }
}
