<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Library\Http\ExternalHttpClient;
use EasyChat\Library\AttachmentPolicy;
use EasyChat\Library\ImageVariantService;
use EasyChat\Library\SafeRuntime;
use EasyChat\Model\Setting;

class AvitoProvider implements MessengerProviderInterface {

    const API_BASE = 'https://api.avito.ru';
    const PROVIDER = 'avito';

    private $registry;
    private $http;

    public function __construct($registry = null, ?ExternalHttpClient $http = null) {
        $this->registry = $registry;
        $this->http = $http ?: new ExternalHttpClient($registry);
    }

    public function providerCode(): string {
        return self::PROVIDER;
    }

    



    public function getBotInfo(string $token): array {
        return $this->getBotInfoWithAccessOptions($token, []);
    }

    private function getBotInfoWithAccessOptions(string $token, array $access_options): array {
        $access_token = $this->getAccessToken($token, $access_options);
        if ($access_token === '') {
            return ['success' => false, 'error' => 'auth_failed'];
        }

        return $this->getBotInfoFromAccessToken($access_token);
    }

    private function getBotInfoFromAccessToken(string $access_token): array {
        $result = $this->apiJson('GET', '/core/v1/accounts/self', $access_token);
        if (empty($result['success'])) {
            return $result;
        }

        return [
            'success' => true,
            'id' => (string)($result['json']['id'] ?? ''),
            'name' => (string)($result['json']['name'] ?? ''),
            'profile_url' => (string)($result['json']['profile_url'] ?? ''),
            'json' => $result['json'],
        ];
    }

    


    public function syncWebhook(array $channel, string $token, string $webhook_url, string $secret, array $options = []): array {
        $access_token = $this->getAccessToken($token, $options);
        if ($access_token === '') {
            return ['success' => false, 'error' => 'auth_failed'];
        }
        $me = $this->getBotInfoFromAccessToken($access_token);
        if (empty($me['success'])) {
            return $me;
        }

        $sync = $this->apiJson('POST', '/messenger/v3/webhook', $access_token, [
            'url' => $webhook_url,
        ]);
        if (empty($sync['success'])) {
            return $sync;
        }

        $subscriptions = $this->apiJson('POST', '/messenger/v1/subscriptions', $access_token);
        if (empty($subscriptions['success'])) {
            return [
                'success' => false,
                'error' => 'subscription_verify_failed',
                'http_code' => (int)($subscriptions['http_code'] ?? 0),
                'json' => $subscriptions['json'] ?? null,
            ];
        }
        if (!$this->hasSubscriptionUrl($subscriptions['json'] ?? [], $webhook_url)) {
            return [
                'success' => false,
                'error' => 'subscription_not_found',
                'http_code' => (int)($subscriptions['http_code'] ?? 0),
                'json' => $subscriptions['json'] ?? null,
            ];
        }

        return [
            'success' => true,
            'bot' => [
                'user_id' => $me['id'],
                'name' => $me['name'],
                'username' => '',
            ],
            'response' => $sync['json'],
            'subscriptions' => $subscriptions['json'],
            'http_code' => $sync['http_code'],
        ];
    }

    public function listSubscriptions(string $token): array {
        $access_token = $this->getAccessToken($token);
        if ($access_token === '') {
            return ['success' => false, 'error' => 'auth_failed'];
        }
        return $this->apiJson('POST', '/messenger/v1/subscriptions', $access_token);
    }

    






    public function getChatUserInfo(string $token, string $account_user_id, string $chat_id, string $author_id): array {
        $account_user_id = preg_replace('/\D+/', '', $account_user_id);
        $chat_id = trim($chat_id);
        $author_id = preg_replace('/\D+/', '', $author_id);
        $result = $this->getChatInfo($token, $account_user_id, $chat_id);
        if (empty($result['success'])) {
            return $result;
        }

        $chat = is_array($result['chat'] ?? null) ? $result['chat'] : [];
        $profile = $this->chatUserProfile($chat, $author_id, $account_user_id);
        if (empty($profile)) {
            return [
                'success' => false,
                'error' => 'chat_user_not_found',
                'http_code' => (int)($result['http_code'] ?? 0),
            ];
        }

        return [
            'success' => true,
            'display_name' => (string)($profile['display_name'] ?? ''),
            'username' => '',
            'profile_url' => (string)($profile['profile_url'] ?? ''),
            'avatar_url' => (string)($profile['avatar_url'] ?? ''),
            'item_context' => is_array($result['item_context'] ?? null) ? $result['item_context'] : [],
            'chat_context' => is_array($result['chat_context'] ?? null) ? $result['chat_context'] : [],
            'http_code' => (int)($result['http_code'] ?? 0),
        ];
    }

    public function getChatInfo(string $token, string $account_user_id, string $chat_id): array {
        $account_user_id = preg_replace('/\D+/', '', $account_user_id);
        $chat_id = trim($chat_id);
        if ($account_user_id === '' || $chat_id === '') {
            return ['success' => false, 'error' => 'chat_lookup_required'];
        }

        $access_token = $this->getAccessToken($token);
        if ($access_token === '') {
            return ['success' => false, 'error' => 'auth_failed'];
        }

        $path = '/messenger/v2/accounts/' . rawurlencode($account_user_id) . '/chats/' . rawurlencode($chat_id);
        $result = $this->apiJson('GET', $path, $access_token);
        if (empty($result['success'])) {
            return $result;
        }

        $chat = is_array($result['json'] ?? null) ? $result['json'] : [];
        return [
            'success' => true,
            'chat' => $chat,
            'chat_context' => $this->chatContext($chat),
            'item_context' => $this->chatItemContext($chat),
            'http_code' => (int)($result['http_code'] ?? 0),
        ];
    }

    public function getChatMessages(string $token, string $account_user_id, string $chat_id, int $limit = 100, int $offset = 0): array {
        $account_user_id = preg_replace('/\D+/', '', $account_user_id);
        $chat_id = trim($chat_id);
        $limit = max(1, min(100, (int)$limit));
        $offset = max(0, min(1000, (int)$offset));
        if ($account_user_id === '' || $chat_id === '') {
            return ['success' => false, 'error' => 'messages_lookup_required'];
        }

        $access_token = $this->getAccessToken($token);
        if ($access_token === '') {
            return ['success' => false, 'error' => 'auth_failed'];
        }

        $path = '/messenger/v3/accounts/' . rawurlencode($account_user_id)
            . '/chats/' . rawurlencode($chat_id)
            . '/messages/?limit=' . $limit . '&offset=' . $offset;
        $result = $this->apiJson('GET', $path, $access_token);
        if (empty($result['success'])) {
            return $result;
        }

        return [
            'success' => true,
            'messages' => $this->messagesFromResponse(is_array($result['json'] ?? null) ? $result['json'] : []),
            'http_code' => (int)($result['http_code'] ?? 0),
        ];
    }

    public function getItemInfo(string $token, string $account_user_id, string $item_id, array $http_options = []): array {
        $account_user_id = preg_replace('/\D+/', '', $account_user_id);
        $item_id = preg_replace('/[^\w:-]+/u', '', trim($item_id));
        if ($account_user_id === '' || $item_id === '') {
            return ['success' => false, 'error' => 'item_lookup_required'];
        }

        $access_token = $this->getAccessToken($token, $http_options);
        if ($access_token === '') {
            return ['success' => false, 'error' => 'auth_failed'];
        }

        $path = '/core/v1/accounts/' . rawurlencode($account_user_id) . '/items/' . rawurlencode($item_id) . '/';
        $result = $this->apiJson('GET', $path, $access_token, null, $http_options);
        if (empty($result['success'])) {
            return $result;
        }

        $json = is_array($result['json'] ?? null) ? $result['json'] : [];
        $item_context = $this->itemInfoContext($json, $item_id);
        return [
            'success' => true,
            'item_context' => $item_context,
            'http_code' => (int)($result['http_code'] ?? 0),
        ];
    }

    


    public function parseWebhook(array $payload): array {
        $webhook_id = (string)($payload['id'] ?? '');
        $update_type = (string)($payload['payload']['type'] ?? '');
        $value = is_array($payload['payload']['value'] ?? null) ? $payload['payload']['value'] : [];

        $message_type = (string)($value['type'] ?? '');
        $content = is_array($value['content'] ?? null) ? $value['content'] : [];
        $text = $this->messageText($message_type, $content);
        $external_user_id = (string)($value['author_id'] ?? '');
        $external_chat_id = (string)($value['chat_id'] ?? '');
        $external_message_id = (string)($value['id'] ?? '');
        $dedupe_key = $webhook_id !== ''
            ? 'avito:webhook:' . $webhook_id
            : ($external_message_id !== '' ? 'avito:message:' . $external_message_id : '');

         
         
         
        $display_name = 'Пользователь Авито';

        return [
            'update_type' => $update_type,
            'text' => $text,
            'external_user_id' => $external_user_id,
            'external_chat_id' => $external_chat_id,
            'external_message_id' => $external_message_id,
            'dedupe_key' => $dedupe_key,
            'recipient_user_id' => (string)($value['user_id'] ?? ''),
            'item_id' => (string)($value['item_id'] ?? ''),
            'chat_type' => (string)($value['chat_type'] ?? ''),
            'created' => (int)($value['created'] ?? 0),
            'published_at' => (string)($value['published_at'] ?? ''),
            'message_type' => $message_type,
            'direction' => 'in',
            'chat_context' => $this->eventChatContext($value, $content),
            'item_context' => $this->messageItemContext($content),
            'display_name' => $display_name,
            'username' => '',
            'profile_url' => '',
            'attachments' => $this->extractAttachments($message_type, $content),
        ];
    }

    public function normalizeHistoryMessage(array $message, string $account_user_id, string $external_chat_id, array $chat_context = []): array {
        $message_type = (string)($message['type'] ?? '');
        $content = is_array($message['content'] ?? null) ? $message['content'] : [];
        $author_id = preg_replace('/\D+/', '', (string)($message['author_id'] ?? ''));
        $account_user_id = preg_replace('/\D+/', '', (string)$account_user_id);
        $direction = (string)($message['direction'] ?? '');
        if (!in_array($direction, ['in', 'out'], true)) {
            $direction = ($account_user_id !== '' && $author_id === $account_user_id) ? 'out' : 'in';
        }

        $item_context = $this->messageItemContext($content);
        if (!$item_context && $this->isItemChatContext($chat_context)) {
            $item_context = $chat_context;
        }
        $context_kind = (string)($chat_context['context_kind'] ?? '');
        $chat_type = (string)($message['chat_type'] ?? '');
        if ($chat_type === '') {
            $chat_type = $item_context ? 'u2i' : ($context_kind === 'general' ? (string)($chat_context['chat_type'] ?? 'u2u') : '');
        }

        return [
            'update_type' => 'history',
            'text' => $this->messageText($message_type, $content),
            'external_user_id' => $author_id,
            'external_chat_id' => $external_chat_id,
            'external_message_id' => (string)($message['id'] ?? ''),
            'recipient_user_id' => $account_user_id,
            'item_id' => (string)($message['item_id'] ?? $item_context['id'] ?? $item_context['item_id'] ?? ''),
            'chat_type' => $chat_type,
            'created' => (int)($message['created'] ?? 0),
            'published_at' => '',
            'message_type' => $message_type,
            'direction' => $direction,
            'chat_context' => $chat_context,
            'item_context' => $item_context,
            'display_name' => '',
            'username' => '',
            'profile_url' => '',
            'attachments' => $this->extractAttachments($message_type, $content),
        ];
    }

    


    public function sendMessage(string $token, string $external_user_id, string $text, array $attachments = [], array $options = []): array {
        $access_token = $this->getAccessToken($token);
        if ($access_token === '') {
            return ['success' => false, 'error' => 'auth_failed'];
        }

        $model_messenger = new \EasyChat\Model\Messenger($this->registry);
        $channel_id = (int)($options['channel_id'] ?? 0);
        $channel = $channel_id > 0
            ? $model_messenger->getChannelById($channel_id)
            : $model_messenger->getChannelByProvider(self::PROVIDER);
        if (!$channel) {
            return ['success' => false, 'error' => 'channel_not_found'];
        }

        $my_user_id = (string)($channel['bot_user_id'] ?? '');
        if ($my_user_id === '') {
            return ['success' => false, 'error' => 'bot_user_id_missing'];
        }

        $chat_id = trim((string)($options['external_chat_id'] ?? ''));
        $contact = null;
        if ($chat_id === '') {
            $contact = $model_messenger->findContact((int)$channel['channel_id'], $external_user_id);
            $chat_id = $contact ? (string)$contact['external_chat_id'] : '';
        }
        if ($chat_id === '') {
            return ['success' => false, 'error' => 'chat_id_missing'];
        }

        if ($attachments) {
            return $this->sendWithAttachments($access_token, $my_user_id, $chat_id, $text, $attachments);
        }

        return $this->sendTextMessage($access_token, $my_user_id, $chat_id, $text);
    }

    private function sendWithAttachments(string $access_token, string $my_user_id, string $chat_id, string $text, array $attachments): array {
        $external_message_ids = [];
        $sent_count = 0;
        foreach (array_values($attachments) as $index => $attachment) {
            if (!is_array($attachment)) {
                continue;
            }
             
             
             
            $result = $this->sendWithAttachment(
                $access_token,
                $my_user_id,
                $chat_id,
                $index === 0 ? $text : '',
                $attachment
            );
            $result_external_ids = is_array($result['external_message_ids'] ?? null)
                ? $result['external_message_ids']
                : [(string)($result['external_message_id'] ?? '')];
            foreach ($result_external_ids as $result_external_id) {
                $result_external_id = trim((string)$result_external_id);
                if ($result_external_id !== '') {
                    $external_message_ids[] = $result_external_id;
                }
            }
            if (empty($result['success'])) {
                if ($sent_count > 0 || !empty($result['partial_delivery'])) {
                    $result['partial_delivery'] = true;
                    $result['sent_attachment_count'] = $sent_count;
                    $result['requested_attachment_count'] = count($attachments);
                    $result['external_message_id'] = (string)($external_message_ids[0] ?? '');
                    $result['external_message_ids'] = array_values(array_unique($external_message_ids));
                }
                return $result;
            }
            $sent_count++;
        }

        if ($sent_count === 0) {
            return ['success' => false, 'error' => 'attachment_type_unsupported'];
        }

        return [
            'success' => true,
            'http_code' => 200,
            'external_message_id' => (string)($external_message_ids[0] ?? ''),
            'external_message_ids' => array_values(array_unique($external_message_ids)),
            'sent_attachment_count' => $sent_count,
            'requested_attachment_count' => count($attachments),
        ];
    }

    private function sendTextMessage(string $access_token, string $my_user_id, string $chat_id, string $text): array {
        $text = $this->truncateUtf8(trim($text), 1000);
        if ($text === '') {
            return ['success' => false, 'error' => 'message_empty'];
        }

        $path = '/messenger/v1/accounts/' . rawurlencode($my_user_id) . '/chats/' . rawurlencode($chat_id) . '/messages';
        $result = $this->apiJson('POST', $path, $access_token, [
            'message' => [
                'text' => $text,
            ],
            'type' => 'text',
        ]);
        if (empty($result['success'])) {
            return $result;
        }

        return [
            'success' => true,
            'http_code' => $result['http_code'],
            'external_message_id' => $this->externalMessageIdFromResponse($result['json'] ?? []),
            'json' => $result['json'],
        ];
    }

    private function sendWithAttachment(string $access_token, string $my_user_id, string $chat_id, string $text, array $attachment): array {
        if ($this->attachmentKind($attachment) === 'document') {
            return $this->sendWithDocumentLink($access_token, $my_user_id, $chat_id, $text, $attachment);
        }

        $text_result = null;
        if (trim($text) !== '') {
             
             
             
            $text_result = $this->sendTextMessage($access_token, $my_user_id, $chat_id, $text);
            if (empty($text_result['success'])) {
                return $text_result;
            }
        }

        $prepared = $this->prepareOutboundImage($attachment);
        if (empty($prepared['success'])) {
            return $this->withPartialTextDelivery($prepared, $text_result);
        }

        $upload = $this->uploadImage($access_token, $my_user_id, $prepared);
        $this->cleanupPreparedImage($prepared);
        if (empty($upload['success'])) {
            return $this->withPartialTextDelivery($upload, $text_result);
        }

        $image_id = (string)($upload['image_id'] ?? '');
        if ($image_id === '') {
            return $this->withPartialTextDelivery(
                ['success' => false, 'error' => 'upload_image_id_missing', 'http_code' => $upload['http_code'] ?? 0],
                $text_result
            );
        }

        $path = '/messenger/v1/accounts/' . rawurlencode($my_user_id) . '/chats/' . rawurlencode($chat_id) . '/messages/image';
        $image_result = $this->apiJson('POST', $path, $access_token, ['image_id' => $image_id], ['timeout' => 20]);
        if (empty($image_result['success'])) {
            return $this->withPartialTextDelivery($image_result, $text_result);
        }

        $text_external_id = is_array($text_result) ? trim((string)($text_result['external_message_id'] ?? '')) : '';
        $image_external_id = $this->externalMessageIdFromResponse($image_result['json'] ?? []);

        return [
            'success' => true,
            'http_code' => $image_result['http_code'],
            'external_message_id' => $text_external_id !== '' ? $text_external_id : $image_external_id,
            'external_message_ids' => array_values(array_filter([$text_external_id, $image_external_id])),
            'json' => [
                'text' => is_array($text_result) ? ($text_result['json'] ?? null) : null,
                'image' => $image_result['json'] ?? null,
            ],
        ];
    }

    private function withPartialTextDelivery(array $failure, $text_result): array {
        if (!is_array($text_result) || empty($text_result['success'])) {
            return $failure;
        }
        $failure['partial_delivery'] = true;
        $failure['sent_text'] = true;
        $failure['external_message_id'] = (string)($text_result['external_message_id'] ?? '');
        $failure['external_message_ids'] = array_values(array_filter([$failure['external_message_id']]));
        return $failure;
    }

    private function sendWithDocumentLink(string $access_token, string $my_user_id, string $chat_id, string $text, array $attachment): array {
        $public_url = trim((string)($attachment['public_url'] ?? ''));
        if ($public_url === '' || stripos($public_url, 'https://') !== 0) {
            return ['success' => false, 'error' => 'attachment_public_url_missing'];
        }

        $display_name = $this->attachmentDisplayName($attachment);
        $message_text = $this->documentLinkText($text, $display_name, $public_url);
        if ($message_text === '') {
            return ['success' => false, 'error' => 'message_empty'];
        }

        $text_result = $this->sendTextMessage($access_token, $my_user_id, $chat_id, $message_text);
        if (empty($text_result['success'])) {
            return $text_result;
        }

        $preview_result = null;
        $preview = is_array($attachment['pdf_preview_image'] ?? null) ? $attachment['pdf_preview_image'] : [];
        if ($preview) {
            $prepared = $this->prepareOutboundImage($preview);
            if (!empty($prepared['success'])) {
                $upload = $this->uploadImage($access_token, $my_user_id, $prepared);
                $this->cleanupPreparedImage($prepared);
                if (!empty($upload['success']) && !empty($upload['image_id'])) {
                    $path = '/messenger/v1/accounts/' . rawurlencode($my_user_id) . '/chats/' . rawurlencode($chat_id) . '/messages/image';
                    $preview_result = $this->apiJson('POST', $path, $access_token, ['image_id' => (string)$upload['image_id']], ['timeout' => 20]);
                } else {
                    $preview_result = $upload;
                }
            } else {
                $preview_result = $prepared;
            }
        }

        return [
            'success' => true,
            'http_code' => (int)($text_result['http_code'] ?? 0),
            'external_message_id' => (string)($text_result['external_message_id'] ?? ''),
            'json' => [
                'text' => $text_result['json'] ?? null,
                'pdf_link' => true,
                'preview' => $preview_result,
            ],
        ];
    }

    private function attachmentKind(array $attachment): string {
        $meta = json_decode((string)($attachment['meta_json'] ?? ''), true);
        if (is_array($meta) && (string)($meta['kind'] ?? '') === 'document') {
            return 'document';
        }

        $filename = (string)($attachment['file_name'] ?? $attachment['filename'] ?? '');
        $mime = (string)($attachment['mime_type'] ?? $attachment['mime'] ?? '');
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        if ($extension === '' && $mime !== '') {
            $extension = AttachmentPolicy::extensionForMime($mime);
        }

        return AttachmentPolicy::isDocumentExtension($extension) || AttachmentPolicy::isDocumentMime($mime)
            ? 'document'
            : 'image';
    }

    private function attachmentDisplayName(array $attachment): string {
        $meta = json_decode((string)($attachment['meta_json'] ?? ''), true);
        $name = is_array($meta) ? trim((string)($meta['display_name'] ?? '')) : '';
        if ($name === '') {
            $name = trim((string)($attachment['display_name'] ?? $attachment['file_name'] ?? $attachment['filename'] ?? ''));
        }
        return basename($name !== '' ? $name : 'document');
    }

    private function documentLinkText(string $text, string $display_name, string $public_url): string {
        $text = trim($text);
        $display_name = trim($display_name);
        $label = $display_name !== ''
            ? 'Документ "' . $display_name . '":'
            : 'Документ:';
        $link_block = $label . "\n" . $public_url;
        $candidate = $text !== '' ? $text . "\n\n" . $link_block : $link_block;
        if ($this->utf8Length($candidate) <= 1000) {
            return $candidate;
        }

        if ($this->utf8Length($link_block) > 1000) {
            return '';
        }

        $available = 1000 - $this->utf8Length("\n\n" . $link_block);
        return trim($this->truncateUtf8($text, max(0, $available))) . "\n\n" . $link_block;
    }

    private function utf8Length(string $value): int {
        if (function_exists('mb_strlen')) {
            return (int)mb_strlen($value, 'UTF-8');
        }
        if (function_exists('iconv_strlen')) {
            $length = iconv_strlen($value, 'UTF-8');
            if ($length !== false) {
                return (int)$length;
            }
        }
        $characters = preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY);
        return is_array($characters) ? count($characters) : strlen($value);
    }

    private function truncateUtf8(string $value, int $limit): string {
        if ($limit <= 0 || $value === '') {
            return '';
        }
        if (function_exists('mb_substr')) {
            return (string)mb_substr($value, 0, $limit, 'UTF-8');
        }
        if (function_exists('iconv_substr')) {
            $result = iconv_substr($value, 0, $limit, 'UTF-8');
            if ($result !== false) {
                return (string)$result;
            }
        }
        $characters = preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY);
        return is_array($characters)
            ? implode('', array_slice($characters, 0, $limit))
            : substr($value, 0, $limit);
    }

    private function prepareOutboundImage(array $attachment): array {
        $filename = (string)($attachment['file_name'] ?? $attachment['filename'] ?? '');
        $path = (string)($attachment['path'] ?? '');
        $mime = (string)($attachment['mime_type'] ?? $attachment['mime'] ?? '');
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        if ($extension === '' && $mime !== '') {
            $extension = AttachmentPolicy::extensionForMime($mime);
        }

        if (!is_file($path)) {
            return ['success' => false, 'error' => 'attachment_file_missing'];
        }

        if (!in_array($extension, AttachmentPolicy::allowedImageExtensions(true), true)) {
            return ['success' => false, 'error' => 'attachment_type_unsupported'];
        }

         
         
        if ($extension === 'webp') {
            $fallback = (new ImageVariantService())->createJpegFallback($path, $filename);
            if (empty($fallback['success'])) {
                return ['success' => false, 'error' => 'attachment_avito_jpeg_fallback_failed'];
            }
            return [
                'success' => true,
                'path' => (string)$fallback['path'],
                'filename' => (string)$fallback['filename'],
                'mime' => (string)$fallback['mime'],
                'temporary' => !empty($fallback['temporary']),
            ];
        }

        $mime = $mime !== '' ? $mime : AttachmentPolicy::mimeForExtension($extension);
        return [
            'success' => true,
            'path' => $path,
            'filename' => $filename !== '' ? $filename : ('image.' . $extension),
            'mime' => $mime,
            'temporary' => false,
        ];
    }

    private function cleanupPreparedImage(array $prepared): void {
        if (!empty($prepared['temporary']) && !empty($prepared['path'])) {
            SafeRuntime::unlink((string)$prepared['path']);
        }
    }

    private function uploadImage(string $access_token, string $my_user_id, array $prepared): array {
        $response = $this->http->request('POST', self::API_BASE . '/messenger/v1/accounts/' . rawurlencode($my_user_id) . '/uploadImages', [
            'headers' => [
                'Accept: application/json',
                'Authorization: Bearer ' . trim($access_token),
            ],
            'post_fields' => [
                'uploadfile[]' => new \CURLFile((string)$prepared['path'], (string)$prepared['mime'], (string)$prepared['filename']),
            ],
            'connect_timeout' => 4,
            'timeout' => 30,
            'trust_profile' => self::PROVIDER,
        ]);

        $raw = $response['raw'];
        $http_code = (int)($response['http_code'] ?? 0);
        if (empty($response['transport_ok']) || $raw === '') {
            return [
                'success' => false,
                'error' => 'upload_failed',
                'http_code' => $http_code,
                'details' => !empty($response['curl_error']) ? 'curl' : 'empty',
            ];
        }

        $json = json_decode((string)$raw, true);
        if (!is_array($json)) {
            return ['success' => false, 'error' => 'upload_bad_json', 'http_code' => $http_code];
        }

        if ($http_code < 200 || $http_code >= 300) {
            $error = $json['error']['message'] ?? $json['error'] ?? 'upload_http_error';
            return [
                'success' => false,
                'error' => is_string($error) && $error !== '' ? $error : 'upload_http_error',
                'http_code' => $http_code,
                'json' => $json,
            ];
        }

        $image_id = $this->uploadedImageIdFromResponse($json);
        if ($image_id === '') {
            return ['success' => false, 'error' => 'upload_image_id_missing', 'http_code' => $http_code, 'json' => $json];
        }

        return ['success' => true, 'http_code' => $http_code, 'image_id' => $image_id, 'json' => $json];
    }

    private function uploadedImageIdFromResponse(array $json): string {
        foreach (['image_id', 'imageId', 'id'] as $key) {
            if (isset($json[$key]) && is_scalar($json[$key]) && (string)$json[$key] !== '') {
                return (string)$json[$key];
            }
        }

        $mapped_id = $this->uploadedImageIdFromMap($json);
        if ($mapped_id !== '') {
            return $mapped_id;
        }

        foreach (['images', 'items', 'result', 'data'] as $key) {
            if (!isset($json[$key]) || !is_array($json[$key])) {
                continue;
            }
            if ($key === 'images') {
                $mapped_id = $this->uploadedImageIdFromMap($json[$key]);
                if ($mapped_id !== '') {
                    return $mapped_id;
                }
            }
            $nested = $this->uploadedImageIdFromResponse($json[$key]);
            if ($nested !== '') {
                return $nested;
            }
        }

        if (array_keys($json) === range(0, count($json) - 1)) {
            foreach ($json as $item) {
                if (is_array($item)) {
                    $nested = $this->uploadedImageIdFromResponse($item);
                    if ($nested !== '') {
                        return $nested;
                    }
                }
            }
        }

        return '';
    }

    private function uploadedImageIdFromMap(array $images): string {
        foreach ($images as $key => $value) {
            if (in_array((string)$key, ['images', 'items', 'result', 'data', 'error', 'message'], true)) {
                continue;
            }
            if ((string)$key === '' || is_int($key) && array_keys($images) === range(0, count($images) - 1)) {
                continue;
            }
            if (is_array($value) || is_scalar($value) || $value === null) {
                return (string)$key;
            }
        }

        return '';
    }

    private function externalMessageIdFromResponse(array $json): string {
        foreach (['id', 'message_id', 'messageId'] as $key) {
            if (isset($json[$key]) && is_scalar($json[$key])) {
                return (string)$json[$key];
            }
        }
        return '';
    }

    


    private function getAccessToken(string $token_creds_json, array $http_options = []): string {
        $creds = json_decode($token_creds_json, true);
        if (!is_array($creds)) {
            return '';
        }
        $client_id = trim((string)($creds['client_id'] ?? ''));
        $client_secret = trim((string)($creds['client_secret'] ?? ''));
        if ($client_id === '' || $client_secret === '') {
            return '';
        }

         
        $model_messenger = new \EasyChat\Model\Messenger($this->registry);
        $channel = $model_messenger->getChannelByProvider(self::PROVIDER);
        $persist_credentials = !array_key_exists('persist_credentials', $http_options)
            || $http_options['persist_credentials'] !== false;
        if ($channel && $persist_credentials) {
            $channel_creds = json_decode((string)$channel['credentials_json'], true);
            if (is_array($channel_creds)) {
                $stored_client_id = $this->decryptStoredSecret((string)($channel_creds['client_id'] ?? ''));
                $stored_client_secret = $this->decryptStoredSecret((string)($channel_creds['client_secret'] ?? ''));
                $cached_token = $this->decryptStoredSecret((string)($channel_creds['access_token'] ?? ''));
                $expires = (int)($channel_creds['access_token_expires'] ?? 0);
                if ($stored_client_id === $client_id && $stored_client_secret === $client_secret && $cached_token !== '' && $expires > (time() + 60)) {
                    return $cached_token;
                }
            }
        }

         
        $response = $this->http->request('POST', self::API_BASE . '/token', [
            'headers' => [
                'Content-Type: application/x-www-form-urlencoded',
                'Accept: application/json',
            ],
            'body' => http_build_query([
                'grant_type' => 'client_credentials',
                'client_id' => $client_id,
                'client_secret' => $client_secret,
            ]),
            'connect_timeout' => isset($http_options['connect_timeout']) ? (float)$http_options['connect_timeout'] : 4,
            'timeout' => isset($http_options['oauth_timeout']) ? (float)$http_options['oauth_timeout'] : (isset($http_options['timeout']) ? (float)$http_options['timeout'] : 10),
            'trust_profile' => self::PROVIDER,
        ]);

        $raw = $response['raw'];
        if (empty($response['transport_ok']) || $raw === '') {
            return '';
        }

        $json = json_decode((string)$raw, true);
        if (!is_array($json) || empty($json['access_token'])) {
            return '';
        }

        $access_token = (string)$json['access_token'];
        $expires_in = (int)($json['expires_in'] ?? 86400);

         
        if ($channel) {
            $channel_creds = json_decode((string)$channel['credentials_json'], true);
            if (!is_array($channel_creds)) {
                $channel_creds = [];
            }
            $encrypted_client_id = $this->encryptStoredSecret($client_id);
            $encrypted_client_secret = $this->encryptStoredSecret($client_secret);
            $encrypted_access_token = $this->encryptStoredSecret($access_token);
            if ($encrypted_client_id !== '') {
                $channel_creds['client_id'] = $encrypted_client_id;
            }
            if ($encrypted_client_secret !== '') {
                $channel_creds['client_secret'] = $encrypted_client_secret;
            }
            $channel_creds['access_token'] = $encrypted_access_token !== '' ? $encrypted_access_token : $access_token;
            $channel_creds['access_token_expires'] = time() + $expires_in;
            $model_messenger->updateChannel((int)$channel['channel_id'], [
                'credentials_json' => json_encode($channel_creds, JSON_UNESCAPED_UNICODE),
            ]);
        }

        return $access_token;
    }

    


    private function apiJson(string $method, string $path, string $access_token, ?array $body = null, array $http_options = []): array {
        $headers = [
            'Accept: application/json',
            'Authorization: Bearer ' . trim($access_token),
        ];
        $request = [
            'headers' => $headers,
            'connect_timeout' => isset($http_options['connect_timeout']) ? (float)$http_options['connect_timeout'] : 4,
            'timeout' => isset($http_options['timeout']) ? (float)$http_options['timeout'] : 12,
            'trust_profile' => self::PROVIDER,
        ];
        if ($body !== null) {
            $headers[] = 'Content-Type: application/json';
            $request['headers'] = $headers;
            $request['body'] = json_encode($body, JSON_UNESCAPED_UNICODE);
        }

        $response = $this->http->request($method, self::API_BASE . $path, $request);
        $raw = $response['raw'];
        $http_code = (int)($response['http_code'] ?? 0);

        if (empty($response['transport_ok']) || $raw === '') {
            return [
                'success' => false,
                'error' => 'api_request_failed',
                'http_code' => $http_code,
                'details' => !empty($response['curl_error']) ? 'curl' : 'empty',
            ];
        }

        $json = json_decode((string)$raw, true);
        if (!is_array($json)) {
            return ['success' => false, 'error' => 'api_bad_json', 'http_code' => $http_code];
        }

        if ($http_code < 200 || $http_code >= 300) {
            $error = $json['error']['message'] ?? $json['error'] ?? 'api_http_error';
            return [
                'success' => false,
                'error' => $error,
                'http_code' => $http_code,
                'json' => $json,
            ];
        }

        return ['success' => true, 'http_code' => $http_code, 'json' => $json];
    }

    private function hasSubscriptionUrl(array $json, string $webhook_url): bool {
        $target = $this->normalizeUrl($webhook_url);
        $items = $this->subscriptionItemsFromResponse($json);
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            if ($this->normalizeUrl((string)($item['url'] ?? '')) === $target) {
                return true;
            }
        }
        return false;
    }

    private function subscriptionItemsFromResponse(array $json): array {
        return $this->listItemsFromResponse($json, ['subscriptions', 'items', 'data']);
    }

    private function messagesFromResponse(array $json): array {
        return $this->listItemsFromResponse($json, ['messages', 'items', 'data']);
    }

    private function listItemsFromResponse(array $json, array $keys): array {
        foreach ($keys as $key) {
            if (isset($json[$key]) && is_array($json[$key])) {
                return array_values(array_filter($json[$key], 'is_array'));
            }
        }

        if (isset($json['result']) && is_array($json['result'])) {
            return $this->listItemsFromResponse($json['result'], $keys);
        }

         
         
        if (array_keys($json) === range(0, count($json) - 1)) {
            return array_values(array_filter($json, 'is_array'));
        }

        return [];
    }

    private function chatUserProfile(array $chat, string $author_id, string $account_user_id): array {
        $users = is_array($chat['users'] ?? null) ? $chat['users'] : [];
        $item_owner_user_id = $this->chatItemOwnerUserId($chat);
        $fallback = [];
        foreach ($users as $user) {
            if (!is_array($user)) {
                continue;
            }
            $user_id = preg_replace('/\D+/', '', (string)($user['id'] ?? $user['user_id'] ?? ''));
            if ($user_id === '') {
                continue;
            }
            if ($fallback === [] && !$this->isSellerUserId($user_id, $account_user_id, $item_owner_user_id)) {
                $fallback = $this->normalizeChatUserProfile($user);
            }
            if ($author_id !== '' && $user_id === $author_id) {
                return $this->normalizeChatUserProfile($user);
            }
        }
        return $fallback;
    }

    private function chatItemOwnerUserId(array $chat): string {
        $context = is_array($chat['context'] ?? null) ? $chat['context'] : [];
        if ((string)($context['type'] ?? '') !== 'item') {
            return '';
        }

        $value = is_array($context['value'] ?? null) ? $context['value'] : [];
        return preg_replace('/\D+/', '', (string)($value['user_id'] ?? ''));
    }

    private function isSellerUserId(string $user_id, string $account_user_id, string $item_owner_user_id): bool {
        $user_id = preg_replace('/\D+/', '', $user_id);
        $account_user_id = preg_replace('/\D+/', '', $account_user_id);
        $item_owner_user_id = preg_replace('/\D+/', '', $item_owner_user_id);

        if ($user_id === '') {
            return true;
        }
        if ($account_user_id !== '' && $user_id === $account_user_id) {
            return true;
        }
        return $item_owner_user_id !== '' && $user_id === $item_owner_user_id;
    }

    private function normalizeChatUserProfile(array $user): array {
        $public_profile = is_array($user['public_user_profile'] ?? null) ? $user['public_user_profile'] : [];
        $avatar = is_array($public_profile['avatar'] ?? null) ? $public_profile['avatar'] : [];
        $avatar_images = is_array($avatar['images'] ?? null) ? $avatar['images'] : [];
        $avatar_url = $this->largestImageUrl($avatar_images);
        if ($avatar_url === '') {
            $avatar_url = trim((string)($avatar['default'] ?? ''));
        }

        return [
            'display_name' => trim((string)($user['name'] ?? '')),
            'profile_url' => trim((string)($public_profile['url'] ?? '')),
            'avatar_url' => $avatar_url,
        ];
    }

    private function chatContext(array $chat): array {
        $context = is_array($chat['context'] ?? null) ? $chat['context'] : [];
        $type = (string)($context['type'] ?? '');
        if ($type === '') {
            return [];
        }

        $value = is_array($context['value'] ?? null) ? $context['value'] : [];
        if ($type === 'item') {
            $item = $this->chatItemContext($chat);
            if (!$item) {
                return [];
            }
            $item['context_kind'] = 'item';
            $item['chat_type'] = 'u2i';
            return $item;
        }

        return array_filter([
            'context_kind' => 'general',
            'chat_type' => $type,
            'title' => (string)($value['title'] ?? ''),
            'url' => (string)($value['url'] ?? ''),
        ], function ($value) {
            return $value !== '' && $value !== [] && $value !== null;
        });
    }

    private function chatItemContext(array $chat): array {
        $context = is_array($chat['context'] ?? null) ? $chat['context'] : [];
        if ((string)($context['type'] ?? '') !== 'item') {
            return [];
        }

        $value = is_array($context['value'] ?? null) ? $context['value'] : [];
        if (empty($value['id'])) {
            return [];
        }

        $image_url = '';
        if (isset($value['images']['main']) && is_array($value['images']['main'])) {
            $image_url = $this->largestImageUrl($value['images']['main']);
        }

        return array_filter([
            'id' => (string)$value['id'],
            'title' => (string)($value['title'] ?? ''),
            'url' => (string)($value['url'] ?? ''),
            'price_string' => (string)($value['price_string'] ?? ''),
            'image_url' => $image_url,
            'status_id' => isset($value['status_id']) ? (string)$value['status_id'] : '',
            'user_id' => isset($value['user_id']) ? (string)$value['user_id'] : '',
            'images' => is_array($value['images'] ?? null) ? $value['images'] : [],
        ], function ($value) {
            return $value !== '' && $value !== [] && $value !== null;
        });
    }

    private function eventChatContext(array $value, array $content): array {
        $item_context = $this->messageItemContext($content);
        if ($item_context) {
            $item_context['context_kind'] = 'item';
            $item_context['chat_type'] = 'u2i';
            return $item_context;
        }

        $chat_type = trim((string)($value['chat_type'] ?? ''));
        $item_id = trim((string)($value['item_id'] ?? ''));
        if ($item_id !== '' || $chat_type === '') {
            return [];
        }

        return [
            'context_kind' => 'general',
            'chat_type' => $chat_type,
        ];
    }

    private function isItemChatContext(array $context): bool {
        return (string)($context['context_kind'] ?? '') === 'item' || trim((string)($context['id'] ?? $context['item_id'] ?? '')) !== '';
    }

    private function messageItemContext(array $content): array {
        $item = is_array($content['item'] ?? null) ? $content['item'] : [];
        if (!$item) {
            return [];
        }

        $url = (string)($item['item_url'] ?? $item['url'] ?? '');
        $item_id = (string)($item['id'] ?? $item['item_id'] ?? '');
        if ($item_id === '') {
            $item_id = $this->itemIdFromUrl($url);
        }

        return array_filter([
            'id' => $item_id,
            'title' => (string)($item['title'] ?? ''),
            'url' => $url,
            'price_string' => (string)($item['price_string'] ?? ''),
            'image_url' => (string)($item['image_url'] ?? ''),
            'status_id' => isset($item['status_id']) ? (string)$item['status_id'] : (string)($item['status'] ?? ''),
            'category' => is_array($item['category'] ?? null) ? $item['category'] : [],
            'description' => (string)($item['description'] ?? ''),
            'location' => is_array($item['location'] ?? null) ? $item['location'] : [],
            'params' => is_array($item['params'] ?? null) ? $item['params'] : [],
        ], function ($value) {
            return $value !== '' && $value !== [] && $value !== null;
        });
    }

    private function itemInfoContext(array $json, string $fallback_item_id): array {
        $item = $json;
        if (isset($json['resources']) && is_array($json['resources']) && isset($json['resources'][0]) && is_array($json['resources'][0])) {
            $item = $json['resources'][0];
        } elseif (isset($json['result']) && is_array($json['result'])) {
            $item = $json['result'];
        } elseif (isset($json['data']) && is_array($json['data'])) {
            $item = $json['data'];
        }

        $id = (string)($item['id'] ?? $item['item_id'] ?? $fallback_item_id);
        $image_url = '';
        if (isset($item['images']['main']) && is_array($item['images']['main'])) {
            $image_url = $this->largestImageUrl($item['images']['main']);
        } elseif (isset($item['images']) && is_array($item['images'])) {
            $image_url = $this->largestImageUrl($item['images']);
        } elseif (is_string($item['image_url'] ?? null)) {
            $image_url = (string)$item['image_url'];
        }

        return array_filter([
            'id' => $id,
            'title' => (string)($item['title'] ?? $item['name'] ?? ''),
            'url' => (string)($item['url'] ?? $item['item_url'] ?? ''),
            'price_string' => (string)($item['price_string'] ?? $item['price'] ?? ''),
            'image_url' => $image_url,
            'status_id' => isset($item['status_id']) ? (string)$item['status_id'] : (string)($item['status'] ?? ''),
            'category' => is_array($item['category'] ?? null) ? $item['category'] : [],
            'description' => (string)($item['description'] ?? ''),
            'location' => is_array($item['location'] ?? null) ? $item['location'] : [],
            'params' => is_array($item['params'] ?? null) ? $item['params'] : [],
        ], function ($value) {
            return $value !== '' && $value !== [] && $value !== null;
        });
    }

    private function itemIdFromUrl(string $url): string {
        $path = (string)(parse_url(trim($url), PHP_URL_PATH) ?: '');
        if ($path === '') {
            return '';
        }
        if (preg_match('/_(\d{5,})(?:\/)?$/', $path, $matches)) {
            return $matches[1];
        }
        if (preg_match('/(?:^|\/)(\d{5,})(?:\/)?$/', $path, $matches)) {
            return $matches[1];
        }
        return '';
    }

    private function normalizeUrl(string $url): string {
        $url = trim($url);
        $parts = parse_url($url);
        if (!is_array($parts) || empty($parts['scheme']) || empty($parts['host'])) {
            return rtrim($url, '/');
        }
        $path = isset($parts['path']) ? rtrim((string)$parts['path'], '/') : '';
        $query = isset($parts['query']) ? '?' . $parts['query'] : '';
        return strtolower((string)$parts['scheme']) . '://' . strtolower((string)$parts['host']) . $path . $query;
    }

    private function messageText(string $type, array $content): string {
        $text = trim((string)($content['text'] ?? ''));
        if ($text !== '') {
            return $text;
        }
        if ($type === 'item' && is_array($content['item'] ?? null)) {
            $item = $content['item'];
            return trim(implode("\n", array_filter([
                (string)($item['title'] ?? ''),
                (string)($item['price_string'] ?? ''),
                (string)($item['item_url'] ?? ''),
            ])));
        }
        if ($type === 'link' && is_array($content['link'] ?? null)) {
            $link = $content['link'];
            return trim(implode("\n", array_filter([
                (string)($link['text'] ?? ''),
                (string)($link['url'] ?? ''),
            ])));
        }
        if ($type === 'location' && is_array($content['location'] ?? null)) {
            $location = $content['location'];
            return trim((string)($location['text'] ?? $location['title'] ?? ''));
        }
        return '';
    }

    private function extractAttachments(string $type, array $content): array {
        if ($type === 'image' && is_array($content['image']['sizes'] ?? null)) {
            $url = $this->largestImageUrl($content['image']['sizes']);
            if ($url === '') {
                return [];
            }
            return [[
                'type' => 'image',
                'url' => $url,
                'payload_hash' => substr(hash('sha256', $url), 0, 16),
            ]];
        }

        if ($type === 'link' || $type === 'text') {
            return $this->extractPdfLinks($this->messageText($type, $content));
        }

        return [];
    }

    private function extractPdfLinks(string $text): array {
        if ($text === '' || !preg_match_all('~https://[^\s<>"\']+~iu', $text, $matches)) {
            return [];
        }

        $attachments = [];
        $seen = [];
        foreach ($matches[0] as $url) {
            $url = rtrim((string)$url, ".,;:!?)\n\r\t ");
            if (!$this->looksLikePdfUrl($url) || isset($seen[$url])) {
                continue;
            }
            $seen[$url] = true;
            $name = basename((string)(parse_url($url, PHP_URL_PATH) ?: 'document.pdf'));
            if ($name === '' || stripos($name, '.pdf') === false) {
                $name = 'document.pdf';
            }
            $attachments[] = [
                'type' => 'document',
                'kind' => 'document',
                'url' => $url,
                'file_name' => $name,
                'payload_hash' => substr(hash('sha256', $url), 0, 16),
            ];
        }
        return $attachments;
    }

    private function looksLikePdfUrl(string $url): bool {
        $parts = parse_url($url);
        if (!is_array($parts) || strtolower((string)($parts['scheme'] ?? '')) !== 'https' || empty($parts['host'])) {
            return false;
        }
        $path = strtolower((string)($parts['path'] ?? ''));
        return substr($path, -4) === '.pdf';
    }

    private function largestImageUrl(array $sizes): string {
        $best_url = '';
        $best_area = 0;
        foreach ($sizes as $size => $url) {
            if (!is_string($url) || trim($url) === '') {
                continue;
            }
            $area = 0;
            if (preg_match('/^(\d+)x(\d+)$/', (string)$size, $matches)) {
                $area = (int)$matches[1] * (int)$matches[2];
            }
            if ($area >= $best_area) {
                $best_area = $area;
                $best_url = trim($url);
            }
        }
        return $best_url;
    }

    private function encryptStoredSecret(string $plain): string {
        if ($plain === '' || !$this->registry) {
            return '';
        }
        try {
            $install_secret = (new Setting($this->registry))->get('chat_install_secret', '');
            return $install_secret !== '' ? SecretBox::encrypt($plain, $install_secret) : '';
        } catch (\Throwable $e) {
            return '';
        }
    }

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