<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Library\AttachmentStorage;
use EasyChat\Library\AppClock;
use EasyChat\Library\Queue\QueueManager;
use EasyChat\Model\Message;
use EasyChat\Model\MessageContext;
use EasyChat\Model\Messenger as MessengerModel;
use EasyChat\Model\Session;
use EasyChat\Model\Setting;








class AvitoHistorySyncService {
    private const PAGE_LIMIT = 100;
     
     
     
    private const MAX_OFFSET = 1000;
    private const DEDUPE_WINDOW_SECONDS = 15;

    private $registry;
    private $db;
    private $prefix;
    private $logger;

    public function __construct($registry) {
        $this->registry = $registry;
        $this->db = $registry->get('db');
        $this->prefix = easychat_db_prefix();
        $this->logger = new MessengerLogger($registry, AvitoProvider::PROVIDER);
    }

    public function enqueue(int $channel_id, int $chat_id, string $reason = 'manual'): bool {
        if ($channel_id <= 0 || $chat_id <= 0) {
            return false;
        }

        try {
            $queue = new QueueManager($this->registry);
            if ($this->hasRecentQueueJob($channel_id, $chat_id)) {
                return false;
            }
            $queue->push('messenger_avito_history_sync', [
                'channel_id' => $channel_id,
                'chat_id' => $chat_id,
                'reason' => mb_substr($reason, 0, 64, 'UTF-8'),
            ], $this->queueDelay($reason), $this->queuePriority($reason));
            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }

    public function enqueueBackfill(int $channel_id = 0, int $limit = 300, string $reason = 'backfill'): int {
        $limit = max(1, min(1000, (int)$limit));
        $channel_filter = $channel_id > 0 ? " AND mc.`channel_id` = " . (int)$channel_id : "";
        $query = $this->db->query(
            "SELECT mc.`channel_id`, mc.`chat_id`
             FROM `{$this->prefix}messenger_contacts` mc
             INNER JOIN `{$this->prefix}messenger_channels` ch ON ch.`channel_id` = mc.`channel_id`
             WHERE ch.`provider` = 'avito'
               AND ch.`status` = 'active'
               AND mc.`provider` = 'avito'
               AND mc.`chat_id` IS NOT NULL
               AND mc.`chat_id` > 0
               AND mc.`external_chat_id` != ''
               {$channel_filter}
             ORDER BY mc.`last_seen_at` DESC, mc.`contact_id` DESC
             LIMIT " . (int)$limit
        );

        $queued = 0;
        foreach ($query->rows as $row) {
            if ($this->enqueue((int)$row['channel_id'], (int)$row['chat_id'], $reason)) {
                $queued++;
            }
        }
        return $queued;
    }

    




    public function enqueueAccessChecks(int $limit = 8): int {
        $limit = max(1, min(20, $limit));
        $lock_name = 'easychat_avito_access_monitor';
        $lock = $this->db->query("SELECT GET_LOCK('" . $this->db->escape($lock_name) . "', 0) AS `locked`");
        if ((int)($lock->row['locked'] ?? 0) !== 1) {
            return 0;
        }
        try {
            $setting = new Setting($this->registry);
            $last_run = (int)$setting->get('chat_avito_access_monitor_at', '0');
            if ($last_run > 0 && (time() - $last_run) < 300) {
                return 0;
            }
             
             
            $setting->set('chat_avito_access_monitor_at', (string)time());
        } finally {
            $this->db->query("SELECT RELEASE_LOCK('" . $this->db->escape($lock_name) . "')");
        }
        $query = $this->db->query(
            "SELECT mc.`channel_id`, mc.`chat_id`
             FROM `{$this->prefix}messenger_contacts` mc
             INNER JOIN `{$this->prefix}messenger_channels` ch ON ch.`channel_id` = mc.`channel_id`
             WHERE ch.`provider` = 'avito' AND ch.`status` = 'active'
               AND mc.`provider` = 'avito' AND mc.`chat_id` > 0 AND mc.`external_chat_id` != ''
             ORDER BY mc.`last_seen_at` DESC, mc.`contact_id` DESC
             LIMIT " . (int)$limit
        );
        $bucket = gmdate('YmdHi', (int)(floor(time() / 300) * 300));
        $queue = new QueueManager($this->registry);
        $queued = 0;
        foreach ((array)($query->rows ?? []) as $row) {
            $channel_id = (int)$row['channel_id'];
            $chat_id = (int)$row['chat_id'];
            $job_id = $queue->pushUnique(
                'messenger_avito_history_sync',
                ['channel_id' => $channel_id, 'chat_id' => $chat_id, 'reason' => 'access_monitor', 'max_offset' => 0],
                'avito_access_' . $channel_id . '_' . $chat_id . '_' . $bucket,
                0,
                4
            );
            if ($job_id > 0) {
                $queued++;
            }
        }
        return $queued;
    }

    public function syncByPayload(array $payload): array {
        return $this->syncChat(
            (int)($payload['channel_id'] ?? 0),
            (int)($payload['chat_id'] ?? 0),
            [
                'reason' => (string)($payload['reason'] ?? 'queue'),
                'max_offset' => (int)($payload['max_offset'] ?? self::MAX_OFFSET),
            ]
        );
    }

    public function syncChat(int $channel_id, int $chat_id, array $options = []): array {
        if ($channel_id <= 0 || $chat_id <= 0) {
            return ['success' => false, 'error' => 'params_missing'];
        }

        $model_messenger = new MessengerModel($this->registry);
        $channel = $model_messenger->getChannelById($channel_id);
        if (!$channel || strtolower((string)($channel['provider'] ?? '')) !== AvitoProvider::PROVIDER) {
            return ['success' => false, 'error' => 'channel_not_found'];
        }

        $max_offset = max(0, min(self::MAX_OFFSET, (int)($options['max_offset'] ?? self::MAX_OFFSET)));
        $contact_resolution = $this->resolveContactForChat($model_messenger, $channel, $chat_id);
        if (empty($contact_resolution['success'])) {
            $skip = (string)($contact_resolution['skip'] ?? 'contact_not_found');
            $this->logger->warning('avito_history_sync_skipped', [
                'channel_id' => $channel_id,
                'chat_id' => $chat_id,
                'skip' => $skip,
                'reason' => (string)($options['reason'] ?? 'sync'),
            ]);
            return [
                'success' => true,
                'skipped' => $skip,
                'error' => (string)($contact_resolution['error'] ?? $skip),
                'imported' => 0,
                'contexts' => 0,
                'total' => 0,
                'history_window_limited' => false,
                'max_offset' => $max_offset,
            ];
        }
        $contact = $contact_resolution['contact'];

        $credentials = json_decode((string)($channel['credentials_json'] ?? ''), true);
        if (!is_array($credentials)) {
            $credentials = [];
        }
        $token = $this->avitoTokenFromCredentials($credentials);
        $account_user_id = preg_replace('/\D+/', '', (string)($channel['bot_user_id'] ?? ''));
        $external_chat_id = trim((string)$contact['external_chat_id']);
        if ($token === '' || $account_user_id === '') {
            return ['success' => false, 'error' => 'auth_missing'];
        }

        $provider = new AvitoProvider($this->registry);
        $incidents = new AvitoAccessIncidentService($this->registry);
        $chat_info = $provider->getChatInfo($token, $account_user_id, $external_chat_id);
        $chat_context = !empty($chat_info['success']) && is_array($chat_info['chat_context'] ?? null)
            ? $chat_info['chat_context']
            : [];

        $messages = [];
        $page_error = null;
        $history_window_limited = false;
        for ($offset = 0; $offset <= $max_offset; $offset += self::PAGE_LIMIT) {
            $page = $provider->getChatMessages($token, $account_user_id, $external_chat_id, self::PAGE_LIMIT, $offset);
            if (empty($page['success'])) {
                $page_error = [
                    'error' => (string)($page['error'] ?? 'sync_failed'),
                    'http_code' => (int)($page['http_code'] ?? 0),
                    'offset' => $offset,
                ];
                $this->logger->warning('avito_history_sync_page_failed', [
                    'channel_id' => $channel_id,
                    'chat_id' => $chat_id,
                    'offset' => $offset,
                    'error' => $page_error['error'],
                    'http_code' => $page_error['http_code'],
                ]);
                if (AvitoAccessIncidentService::isSubscriptionRestriction($page)) {
                    $incidents->observeFailure(
                        $channel_id,
                        (int)($contact['contact_id'] ?? 0),
                        $chat_id,
                        $external_chat_id,
                        $page
                    );
                }
                break;
            }

            $incidents->observeSuccess(
                $channel_id,
                (int)($contact['contact_id'] ?? 0),
                $chat_id,
                $external_chat_id
            );

            $items = is_array($page['messages'] ?? null) ? $page['messages'] : [];
            foreach ($items as $item) {
                if (is_array($item)) {
                    $messages[] = $item;
                }
            }
            if (count($items) >= self::PAGE_LIMIT && $offset >= $max_offset) {
                $history_window_limited = true;
                $this->logger->warning('avito_history_sync_window_limited', [
                    'channel_id' => $channel_id,
                    'chat_id' => $chat_id,
                    'max_offset' => $max_offset,
                    'page_limit' => self::PAGE_LIMIT,
                ]);
                break;
            }
            if (count($items) < self::PAGE_LIMIT) {
                break;
            }
        }

        usort($messages, function ($a, $b) {
            $a_created = (int)($a['created'] ?? 0);
            $b_created = (int)($b['created'] ?? 0);
            if ($a_created === $b_created) {
                return strcmp((string)($a['id'] ?? ''), (string)($b['id'] ?? ''));
            }
            return $a_created <=> $b_created;
        });

        $imported = 0;
        $updated_contexts = 0;
        $repaired_sources = 0;
        foreach ($messages as $raw_message) {
            $event = $provider->normalizeHistoryMessage($raw_message, $account_user_id, $external_chat_id, $chat_context);
            $result = $this->importHistoryEvent($channel, $contact, $chat_id, $event);
            if (!empty($result['imported'])) {
                $imported++;
            }
            if (!empty($result['context_saved'])) {
                $updated_contexts++;
            }
            if (!empty($result['repaired_source'])) {
                $repaired_sources++;
            }
        }
        if ($imported > 0 || $updated_contexts > 0 || $repaired_sources > 0) {
            (new Session($this->registry))->updateModifiedDate($chat_id);
        }

        $this->logger->info('avito_history_sync_done', [
            'channel_id' => $channel_id,
            'chat_id' => $chat_id,
            'contact_id' => (int)($contact['contact_id'] ?? 0),
            'imported' => $imported,
            'contexts' => $updated_contexts,
            'repaired_sources' => $repaired_sources,
            'history_window_limited' => $history_window_limited ? 1 : 0,
            'partial_error' => $page_error ? (string)$page_error['error'] : '',
            'reason' => (string)($options['reason'] ?? 'sync'),
        ]);

        if ($page_error) {
             
             
            if ((int)$page_error['http_code'] === 402) {
                return [
                    'success' => true,
                    'skipped' => AvitoAccessIncidentService::INCIDENT_CODE,
                    'http_code' => 402,
                    'imported' => $imported,
                    'contexts' => $updated_contexts,
                    'total' => count($messages),
                    'history_window_limited' => $history_window_limited,
                    'max_offset' => $max_offset,
                ];
            }
            return [
                'success' => false,
                'error' => 'history_page_failed',
                'page_error' => $page_error['error'],
                'http_code' => (int)$page_error['http_code'],
                'failed_offset' => (int)$page_error['offset'],
                'imported' => $imported,
                'contexts' => $updated_contexts,
                'repaired_sources' => $repaired_sources,
                'total' => count($messages),
                'history_window_limited' => $history_window_limited,
                'max_offset' => $max_offset,
            ];
        }

        return [
            'success' => true,
            'imported' => $imported,
            'contexts' => $updated_contexts,
            'repaired_sources' => $repaired_sources,
            'total' => count($messages),
            'history_window_limited' => $history_window_limited,
            'max_offset' => $max_offset,
        ];
    }

    private function resolveContactForChat(MessengerModel $model_messenger, array $channel, int $chat_id): array {
        $channel_id = (int)($channel['channel_id'] ?? 0);
        $contact = $model_messenger->getContactByChat($channel_id, $chat_id);
        if ($this->isUsableAvitoHistoryContact($contact, $channel_id)) {
            return ['success' => true, 'contact' => $contact];
        }

        $session = (new Session($this->registry))->getSessionById($chat_id);
        if (!$session) {
            return ['success' => false, 'skip' => 'stale_chat', 'error' => 'chat_not_found'];
        }

        $session_provider = strtolower((string)($session['source_provider'] ?? 'widget'));
        if ($session_provider !== AvitoProvider::PROVIDER) {
            return ['success' => false, 'skip' => 'not_avito_chat', 'error' => 'not_avito_chat'];
        }

        $session_channel_id = (int)($session['source_channel_id'] ?? 0);
        if ($session_channel_id > 0 && $session_channel_id !== $channel_id) {
            return ['success' => false, 'skip' => 'channel_mismatch', 'error' => 'channel_mismatch'];
        }

        $contact_id = (int)($session['external_contact_id'] ?? 0);
        if ($contact_id > 0) {
            $candidate = $model_messenger->getContactById($contact_id);
            if ($this->isUsableAvitoHistoryContact($candidate, $channel_id)) {
                if ((int)($candidate['chat_id'] ?? 0) !== $chat_id) {
                    $model_messenger->linkContactToChat($contact_id, $chat_id);
                    $candidate['chat_id'] = $chat_id;
                }
                return ['success' => true, 'contact' => $candidate];
            }
        }

        return ['success' => false, 'skip' => 'contact_not_found', 'error' => 'contact_not_found'];
    }

    private function isUsableAvitoHistoryContact($contact, int $channel_id): bool {
        if (!is_array($contact) || !$contact) {
            return false;
        }
        if ((int)($contact['channel_id'] ?? 0) !== $channel_id) {
            return false;
        }
        if (strtolower((string)($contact['provider'] ?? AvitoProvider::PROVIDER)) !== AvitoProvider::PROVIDER) {
            return false;
        }
        return trim((string)($contact['external_chat_id'] ?? '')) !== '';
    }

    private function importHistoryEvent(array $channel, array $contact, int $chat_id, array $event): array {
        $external_message_id = trim((string)($event['external_message_id'] ?? ''));
        if ($external_message_id === '') {
            return ['skipped' => 'message_id_missing'];
        }

        $channel_id = (int)$channel['channel_id'];
        $model_message = new Message($this->registry);
        $model_messenger = new MessengerModel($this->registry);
        $provider_delivery = $model_messenger->findDeliveryByExternalMessageId($channel_id, $external_message_id);
        if ($provider_delivery) {
            return [
                'skipped' => 'provider_delivery_exists',
                'message_id' => (int)($provider_delivery['message_id'] ?? 0),
            ];
        }
        $existing = $model_message->findByExternalMessageId(AvitoProvider::PROVIDER, $channel_id, $external_message_id);
        $repaired_source = false;
        if (!$existing) {
            $existing = $model_message->findByExternalMessageIdInChat($chat_id, $external_message_id);
            if ($existing) {
                $repaired_source = $this->repairExistingMessageSource($model_message, $existing, $channel_id, $external_message_id);
            }
        }
        if ($existing) {
            $context_saved = $this->saveAvitoContext((int)$existing['message_id'], $channel, $contact, $event, $chat_id);
            return [
                'skipped' => 'exists',
                'message_id' => (int)$existing['message_id'],
                'context_saved' => $context_saved,
                'repaired_source' => $repaired_source,
            ];
        }

        $direction = (string)($event['direction'] ?? 'in');
        $author = $direction === 'out' ? 2 : 1;
        $manager_name = $direction === 'out' ? 'Оператор Авито' : '';
        $text = trim((string)($event['text'] ?? ''));

        $attachments = $this->downloadAttachments($chat_id, $event['attachments'] ?? []);
        $first_image = '';
        foreach ($attachments as $attachment) {
            if (($attachment['kind'] ?? 'image') === 'image' && !empty($attachment['filename'])) {
                $first_image = (string)$attachment['filename'];
                break;
            }
        }
        if ($text === '' && !$attachments) {
            $text = $this->historyPlaceholder((string)($event['message_type'] ?? ''));
        }

        $message_id = $model_message->addMessage([
            'chat_id' => $chat_id,
            'text' => urlencode($text),
            'image' => $first_image,
            'author' => $author,
            'manager_id' => 0,
            'manager_name' => $manager_name,
            'status_user' => $author === 1 ? 1 : 0,
            'status_admin' => 1,
            'ip' => 'avito',
            'source_provider' => AvitoProvider::PROVIDER,
            'source_channel_id' => $channel_id,
            'external_message_id' => $external_message_id,
            'date_added' => $this->eventDate((int)($event['created'] ?? 0)),
            'skip_metrics' => true,
            'touch_chat' => false,
        ]);

        $storage = new AttachmentStorage($this->registry);
        foreach ($attachments as $attachment) {
            $storage->attachToMessage((int)$message_id, $chat_id, $attachment, AvitoProvider::PROVIDER, [
                'external_payload_hash' => $attachment['external_payload_hash'] ?? '',
            ]);
        }

        $dedupe_key = 'avito:history:' . $external_message_id;
        if (!$model_messenger->findDeliveryByDedupe($channel_id, $dedupe_key)) {
            $model_messenger->createDelivery([
                'channel_id' => $channel_id,
                'contact_id' => (int)($contact['contact_id'] ?? 0),
                'message_id' => (int)$message_id,
                'direction' => $direction === 'out' ? 'out' : 'in',
                'external_message_id' => $external_message_id,
                'dedupe_key' => $dedupe_key,
                'payload_hash' => hash('sha256', json_encode($event, JSON_UNESCAPED_UNICODE)),
                'status' => $direction === 'out' ? 'sent' : 'received',
            ]);
        }

        $context_saved = $this->saveAvitoContext((int)$message_id, $channel, $contact, $event, $chat_id);
        return ['imported' => true, 'message_id' => (int)$message_id, 'context_saved' => $context_saved];
    }

    private function repairExistingMessageSource(Message $model_message, array $message, int $channel_id, string $external_message_id): bool {
        $message_id = (int)($message['message_id'] ?? 0);
        if ($message_id <= 0 || $channel_id <= 0 || trim($external_message_id) === '') {
            return false;
        }

        $source_provider = strtolower((string)($message['source_provider'] ?? ''));
        $source_channel_id = (int)($message['source_channel_id'] ?? 0);
        if ($source_provider === AvitoProvider::PROVIDER && $source_channel_id === $channel_id) {
            return false;
        }

         
         
         
         
        $model_message->updateExternalMessageId($message_id, $external_message_id, AvitoProvider::PROVIDER, $channel_id);
        return true;
    }

    private function hasRecentQueueJob(int $channel_id, int $chat_id): bool {
        $since = time() - self::DEDUPE_WINDOW_SECONDS;
        $query = $this->db->query(
            "SELECT `payload`
             FROM `{$this->prefix}queue`
             WHERE `job_type` = 'messenger_avito_history_sync'
               AND `created_at` >= " . (int)$since . "
               AND `status` IN ('pending','processing','done')
             ORDER BY `id` DESC
             LIMIT 50"
        );

        foreach ($query->rows as $row) {
            $payload = json_decode((string)($row['payload'] ?? ''), true);
            if (!is_array($payload)) {
                continue;
            }
            if ((int)($payload['channel_id'] ?? 0) === $channel_id && (int)($payload['chat_id'] ?? 0) === $chat_id) {
                return true;
            }
        }
        return false;
    }

    private function queueDelay(string $reason): int {
        return in_array($reason, ['admin_open', 'android_open', 'manual'], true) ? 0 : 5;
    }

    private function queuePriority(string $reason): int {
        return in_array($reason, ['admin_open', 'android_open'], true) ? 4 : 3;
    }

    private function saveAvitoContext(int $message_id, array $channel, array $contact, array $event, int $chat_id): bool {
        $item_id = trim((string)($event['item_id'] ?? ''));
        $snapshot = is_array($event['item_context'] ?? null) ? $event['item_context'] : [];
        if ($item_id === '' && !empty($snapshot['id'])) {
            $item_id = (string)$snapshot['id'];
        }
        if ($item_id === '') {
            $chat_context = is_array($event['chat_context'] ?? null) ? $event['chat_context'] : [];
            $context_kind = (string)($chat_context['context_kind'] ?? '');
            $chat_type = trim((string)($event['chat_type'] ?? $chat_context['chat_type'] ?? ''));
            if ($context_kind === 'general' || ($chat_type !== '' && $chat_type !== 'u2i')) {
                (new MessageContext($this->registry))->upsertAvitoGeneralContext([
                    'channel_id' => (int)($channel['channel_id'] ?? 0),
                    'chat_id' => $chat_id,
                    'message_id' => $message_id,
                    'external_chat_id' => (string)($event['external_chat_id'] ?? $contact['external_chat_id'] ?? ''),
                    'external_user_id' => (string)($event['external_user_id'] ?? ''),
                    'external_message_id' => (string)($event['external_message_id'] ?? ''),
                    'chat_type' => $chat_type !== '' ? $chat_type : 'u2u',
                    'title' => (string)($chat_context['title'] ?? 'Общий запрос Авито'),
                    'url' => (string)($chat_context['url'] ?? ''),
                    'snapshot' => $chat_context,
                ]);
                return true;
            }
            return false;
        }

        (new MessageContext($this->registry))->upsertAvitoItemContext([
            'channel_id' => (int)($channel['channel_id'] ?? 0),
            'chat_id' => $chat_id,
            'message_id' => $message_id,
            'external_chat_id' => (string)($event['external_chat_id'] ?? $contact['external_chat_id'] ?? ''),
            'external_user_id' => (string)($event['external_user_id'] ?? ''),
            'external_message_id' => (string)($event['external_message_id'] ?? ''),
            'item_id' => $item_id,
            'chat_type' => (string)($event['chat_type'] ?? ''),
            'snapshot' => $snapshot,
        ]);
        (new AvitoItemContextService($this->registry))->remember((int)($channel['channel_id'] ?? 0), array_merge($snapshot, [
            'item_id' => $item_id,
        ]), 'history_context');
        return true;
    }

    private function downloadAttachments(int $chat_id, array $items): array {
        $downloaded = [];
        if (!$items) {
            return $downloaded;
        }

        $max_mb = max(1, (int)(new Setting($this->registry))->get('chat_max_file_size', '5'));
        $storage = new AttachmentStorage($this->registry);
        foreach ($items as $item) {
            if (!is_array($item) || !empty($item['unsupported'])) {
                continue;
            }
            $url = trim((string)($item['url'] ?? ''));
            if ($url === '') {
                continue;
            }
            $result = $storage->saveRemoteAttachment(
                $chat_id,
                $url,
                [],
                $max_mb,
                AvitoProvider::PROVIDER,
                (string)($item['file_name'] ?? $item['filename'] ?? '')
            );
            if (empty($result['success'])) {
                continue;
            }
            $result['external_payload_hash'] = (string)($item['payload_hash'] ?? '');
            $downloaded[] = $result;
        }
        return $downloaded;
    }

    private function historyPlaceholder(string $type): string {
        switch ($type) {
            case 'deleted':
                return '[Сообщение удалено в Авито]';
            case 'voice':
                return '[Голосовое сообщение Авито]';
            case 'call':
                return '[Звонок Авито]';
            default:
                return '[Служебное сообщение Авито]';
        }
    }

    private function eventDate(int $created): string {
        $clock = new AppClock($this->registry);
        if ($created <= 0) {
            return $clock->nowSql();
        }
        return $clock->fromUnixTimestampSql($created);
    }

    private function avitoTokenFromCredentials(array $credentials): string {
        $token = $this->decryptSecretOrPlain((string)($credentials['bot_token'] ?? ''));
        if ($token !== '') {
            return $token;
        }
        $client_id = $this->decryptSecretOrPlain((string)($credentials['client_id'] ?? ''));
        $client_secret = $this->decryptSecretOrPlain((string)($credentials['client_secret'] ?? ''));
        if ($client_id === '' || $client_secret === '') {
            return '';
        }
        return json_encode([
            'client_id' => $client_id,
            'client_secret' => $client_secret,
        ], JSON_UNESCAPED_UNICODE);
    }

    private function decryptSecretOrPlain(string $stored): string {
        $stored = trim($stored);
        if ($stored === '') {
            return '';
        }
        if (strpos($stored, 'enc:v1:') !== 0) {
            return $stored;
        }
        $install_secret = (new Setting($this->registry))->get('chat_install_secret', '');
        return $install_secret !== '' ? SecretBox::decrypt($stored, $install_secret) : '';
    }
}
