<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Model\Messenger as MessengerModel;
use EasyChat\Model\Setting;
use EasyChat\Library\AppClock;
use EasyChat\Library\Http\ProxyPolicy;
use EasyChat\Library\SafeRuntime;

class TelegramPollingService {

    const MODE_WEBHOOK = 'webhook';
    const MODE_POLLING = 'polling';

    private $registry;
    private $logger;
    private $provider_override;
    private $inbound_service_factory;

    public function __construct($registry, ?TelegramProvider $provider = null, ?callable $inbound_service_factory = null) {
        $this->registry = $registry;
        $this->logger = new MessengerLogger($registry, TelegramProvider::PROVIDER);
        $this->provider_override = $provider;
        $this->inbound_service_factory = $inbound_service_factory;
    }

    public function run(array $options = []): array {
        $lock = $this->acquireLock();
        if (!$lock) {
            return ['success' => true, 'skipped' => true, 'reason' => 'locked'];
        }

        try {
            return $this->runUnlocked($options);
        } finally {
            flock($lock, LOCK_UN);
            fclose($lock);
        }
    }

    private function runUnlocked(array $options): array {
        $model_messenger = new MessengerModel($this->registry);
        $channel = $model_messenger->getChannelByProvider(TelegramProvider::PROVIDER);
        if (!$channel || (string)($channel['status'] ?? '') !== 'active') {
            return ['success' => true, 'skipped' => true, 'reason' => 'inactive'];
        }

        $settings = $this->decodeJson((string)($channel['settings_json'] ?? '{}'));
        $credentials = $this->decodeJson((string)($channel['credentials_json'] ?? '{}'));
        $token = $this->decryptSecret((string)($credentials['bot_token'] ?? ''));
        $secret = $this->decryptSecret((string)($credentials['webhook_secret'] ?? ''));
        if ($token === '' || $secret === '') {
            $this->logger->warning('telegram_polling_credentials_missing', [
                'channel_id' => (int)($channel['channel_id'] ?? 0),
            ]);
            return ['success' => false, 'error' => 'credentials_missing'];
        }

        $provider = $this->telegramProvider();
        $mode = (string)($settings['delivery_mode'] ?? self::MODE_WEBHOOK);
        if ($mode !== self::MODE_POLLING) {
            $switch = $this->switchToPollingIfWebhookTimedOut($provider, $token, $channel, $settings);
            if (empty($switch['polling'])) {
                return ['success' => true, 'mode' => self::MODE_WEBHOOK, 'processed' => 0];
            }
            $settings = $switch['settings'];
            $channel['settings_json'] = json_encode($settings, JSON_UNESCAPED_UNICODE);
        }

        return $this->pollUpdates($provider, $model_messenger, $channel, $settings, $token, $secret, $options);
    }

    private function switchToPollingIfWebhookTimedOut(TelegramProvider $provider, string $token, array $channel, array $settings): array {
        $info_attempt = $this->getWebhookInfoWithTransportFallback($provider, $token, $channel);
        $info = $info_attempt['result'];
        if (empty($info['success'])) {
            $this->logger->warning('telegram_webhook_info_failed', [
                'channel_id' => (int)($channel['channel_id'] ?? 0),
                'http_code' => (int)($info['http_code'] ?? 0),
                'details' => (string)($info['details'] ?? ''),
            ]);
            return ['polling' => false, 'settings' => $settings];
        }

        $result = is_array($info['json']['result'] ?? null) ? $info['json']['result'] : [];
        $pending = (int)($result['pending_update_count'] ?? 0);
        $last_error = (string)($result['last_error_message'] ?? '');
        if ($pending <= 0 || stripos($last_error, 'timed out') === false) {
            return ['polling' => false, 'settings' => $settings];
        }

        $delete = $this->deleteWebhookWithTransportFallback($provider, $token, $channel, $info_attempt['options']);
        if (empty($delete['success'])) {
            $this->logger->warning('telegram_webhook_delete_failed', [
                'channel_id' => (int)($channel['channel_id'] ?? 0),
                'http_code' => (int)($delete['http_code'] ?? 0),
                'details' => (string)($delete['details'] ?? ''),
            ]);
            return ['polling' => false, 'settings' => $settings];
        }

        $settings['delivery_mode'] = self::MODE_POLLING;
        $settings['polling_reason'] = 'webhook_connection_timeout';
        $settings['polling_enabled_at'] = (new AppClock($this->registry))->nowSql();
        $settings['webhook_last_error'] = $this->shortText($last_error, 180);
        $settings['polling_transport'] = !empty($info_attempt['direct_retry']) ? 'direct_after_proxy_failure' : 'configured';

        $model_messenger = new MessengerModel($this->registry);
        $model_messenger->updateChannel((int)$channel['channel_id'], [
            'settings_json' => json_encode($settings, JSON_UNESCAPED_UNICODE),
            'last_error' => 'Telegram webhook timed out; polling fallback enabled.',
        ]);

        $this->logger->warning('telegram_polling_enabled', [
            'channel_id' => (int)($channel['channel_id'] ?? 0),
            'pending_updates' => $pending,
            'transport' => !empty($info_attempt['direct_retry']) ? 'direct_after_proxy_failure' : 'configured',
        ]);

        return ['polling' => true, 'settings' => $settings];
    }

    private function pollUpdates(
        TelegramProvider $provider,
        MessengerModel $model_messenger,
        array $channel,
        array $settings,
        string $token,
        string $secret,
        array $options
    ): array {
        $offset = max(0, (int)($settings['polling_offset'] ?? 0));
        $limit = isset($options['limit']) ? (int)$options['limit'] : 25;
        $updates_attempt = $this->getUpdatesWithTransportFallback($provider, $token, $offset, $limit, 0, $channel);
        $updates = $updates_attempt['result'];
        if (empty($updates['success']) && stripos((string)($updates['error'] ?? ''), 'webhook is active') !== false) {
            $this->deleteWebhookWithTransportFallback($provider, $token, $channel, $updates_attempt['options']);
            $updates_attempt = $this->getUpdatesWithTransportFallback($provider, $token, $offset, $limit, 0, $channel);
            $updates = $updates_attempt['result'];
        }

        if (empty($updates['success'])) {
            $error = (string)($updates['error'] ?? 'telegram_get_updates_failed');
            $model_messenger->updateChannel((int)$channel['channel_id'], [
                'last_error' => $this->shortText($error, 512),
            ]);
            $this->logger->warning('telegram_polling_failed', [
                'channel_id' => (int)($channel['channel_id'] ?? 0),
                'http_code' => (int)($updates['http_code'] ?? 0),
                'details' => (string)($updates['details'] ?? ''),
            ]);
            return ['success' => false, 'error' => $error];
        }

        $items = is_array($updates['json']['result'] ?? null) ? $updates['json']['result'] : [];
        $service = $this->inboundService();
        $processed = 0;

        foreach ($items as $update) {
            if (!is_array($update)) {
                continue;
            }
            $update_id = (int)($update['update_id'] ?? 0);
            $raw = json_encode($update, JSON_UNESCAPED_UNICODE);
            $result = $service->handleWebhook(TelegramProvider::PROVIDER, $channel, $update, (string)$raw, $secret);
            if (empty($result['success'])) {
                $error = (string)($result['error'] ?? 'telegram_polling_update_failed');
                $model_messenger->updateChannel((int)$channel['channel_id'], [
                    'last_error' => $this->shortText($error, 512),
                ]);
                $this->logger->warning('telegram_polling_update_failed', [
                    'channel_id' => (int)($channel['channel_id'] ?? 0),
                    'update_id' => $update_id,
                ]);
                return ['success' => false, 'error' => $error, 'processed' => $processed];
            }

            $processed++;
            if ($update_id > 0) {
                $offset = $update_id + 1;
            }
        }

        $settings['delivery_mode'] = self::MODE_POLLING;
        $settings['polling_offset'] = $offset;
        $settings['polling_last_run_at'] = (new AppClock($this->registry))->nowSql();
        $settings['polling_transport'] = !empty($updates_attempt['direct_retry']) ? 'direct_after_proxy_failure' : 'configured';
        $model_messenger->updateChannel((int)$channel['channel_id'], [
            'settings_json' => json_encode($settings, JSON_UNESCAPED_UNICODE),
            'last_error' => '',
        ]);

        if ($processed > 0) {
            $this->logger->info('telegram_polling_updates_processed', [
                'channel_id' => (int)($channel['channel_id'] ?? 0),
                'processed' => $processed,
            ]);
        }

        return [
            'success' => true,
            'mode' => self::MODE_POLLING,
            'processed' => $processed,
            'next_offset' => $offset,
        ];
    }

    private function getWebhookInfoWithTransportFallback(TelegramProvider $provider, string $token, array $channel): array {
        $result = $provider->getWebhookInfo($token);
        if (!empty($result['success']) || !$this->shouldRetryTelegramDirect($result)) {
            return ['result' => $result, 'options' => [], 'direct_retry' => false];
        }

        $direct_options = $this->directTelegramTransportOptions();
        $direct = $provider->getWebhookInfo($token, $direct_options);
        if (!empty($direct['success'])) {
            $this->logger->warning('telegram_api_proxy_failed_direct_ok', [
                'channel_id' => (int)($channel['channel_id'] ?? 0),
                'operation' => 'getWebhookInfo',
                'proxy_http_code' => (int)($result['http_code'] ?? 0),
                'proxy_details' => (string)($result['details'] ?? ''),
            ]);
            return ['result' => $direct, 'options' => $direct_options, 'direct_retry' => true];
        }

        $this->logger->warning('telegram_api_direct_retry_failed', [
            'channel_id' => (int)($channel['channel_id'] ?? 0),
            'operation' => 'getWebhookInfo',
            'http_code' => (int)($direct['http_code'] ?? 0),
            'details' => (string)($direct['details'] ?? ''),
        ]);

        return ['result' => $result, 'options' => [], 'direct_retry' => false];
    }

    private function deleteWebhookWithTransportFallback(TelegramProvider $provider, string $token, array $channel, array $preferred_options = []): array {
        $result = $provider->deleteWebhook($token, $preferred_options);
        if (!empty($result['success']) || !empty($preferred_options) || !$this->shouldRetryTelegramDirect($result)) {
            return $result;
        }

        $direct = $provider->deleteWebhook($token, $this->directTelegramTransportOptions());
        if (!empty($direct['success'])) {
            $this->logger->warning('telegram_api_proxy_failed_direct_ok', [
                'channel_id' => (int)($channel['channel_id'] ?? 0),
                'operation' => 'deleteWebhook',
                'proxy_http_code' => (int)($result['http_code'] ?? 0),
                'proxy_details' => (string)($result['details'] ?? ''),
            ]);
            return $direct;
        }

        return $result;
    }

    private function getUpdatesWithTransportFallback(
        TelegramProvider $provider,
        string $token,
        int $offset,
        int $limit,
        int $timeout,
        array $channel
    ): array {
        $result = $provider->getUpdates($token, $offset, $limit, $timeout);
        if (!empty($result['success']) || !$this->shouldRetryTelegramDirect($result)) {
            return ['result' => $result, 'options' => [], 'direct_retry' => false];
        }

        $direct_options = $this->directTelegramTransportOptions();
        $direct = $provider->getUpdates($token, $offset, $limit, $timeout, $direct_options);
        if (!empty($direct['success'])) {
            $this->logger->warning('telegram_api_proxy_failed_direct_ok', [
                'channel_id' => (int)($channel['channel_id'] ?? 0),
                'operation' => 'getUpdates',
                'proxy_http_code' => (int)($result['http_code'] ?? 0),
                'proxy_details' => (string)($result['details'] ?? ''),
            ]);
            return ['result' => $direct, 'options' => $direct_options, 'direct_retry' => true];
        }

        $this->logger->warning('telegram_api_direct_retry_failed', [
            'channel_id' => (int)($channel['channel_id'] ?? 0),
            'operation' => 'getUpdates',
            'http_code' => (int)($direct['http_code'] ?? 0),
            'details' => (string)($direct['details'] ?? ''),
        ]);

        return ['result' => $result, 'options' => [], 'direct_retry' => false];
    }

    private function shouldRetryTelegramDirect(array $result): bool {
        if (!empty($result['success'])) {
            return false;
        }
        if (!$this->telegramProxyRouteUsesProxy()) {
            return false;
        }

        $http_code = (int)($result['http_code'] ?? 0);
        $error = strtolower((string)($result['error'] ?? ''));
        $details = strtolower((string)($result['details'] ?? ''));

         
         
         
        return $http_code === 0
            || $details === 'curl'
            || $details === 'empty'
            || $error === 'api_request_failed';
    }

    private function directTelegramTransportOptions(): array {
        return ['proxy' => false];
    }

    private function telegramProxyRouteUsesProxy(): bool {
        try {
            $state = (new ProxyPolicy($this->registry))->routeState('messenger_telegram');
            return (string)($state['code'] ?? '') === 'proxy';
        } catch (\Throwable $e) {
            return false;
        }
    }

    private function telegramProvider(): TelegramProvider {
        return $this->provider_override ?: new TelegramProvider($this->registry);
    }

    private function inboundService() {
        if ($this->inbound_service_factory !== null) {
            return call_user_func($this->inbound_service_factory, $this->registry);
        }
        return new InboundService($this->registry);
    }

    private function shortText(string $value, int $length): string {
        if (function_exists('mb_substr')) {
            return \mb_substr($value, 0, $length, 'UTF-8');
        }
        return substr($value, 0, $length);
    }

    private function decryptSecret(string $encrypted): string {
        $model_setting = new Setting($this->registry);
        return SecretBox::decrypt($encrypted, $model_setting->get('chat_install_secret', ''));
    }

    private function decodeJson(string $value): array {
        $json = json_decode($value !== '' ? $value : '{}', true);
        return is_array($json) ? $json : [];
    }

    private function acquireLock() {
        $path = (defined('DIR_LOGS') ? DIR_LOGS : sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'telegram_polling.lock';
        $handle = SafeRuntime::call(function () use ($path) {
            return fopen($path, 'c');
        }, false);
        if (!$handle) {
            return null;
        }
        if (!flock($handle, LOCK_EX | LOCK_NB)) {
            fclose($handle);
            return null;
        }
        return $handle;
    }
}
