<?php
namespace EasyChat\Library\Messenger;

use EasyChat\Library\Http\ExternalHttpClient;

class YoulaProvider implements MessengerProviderInterface {
    const API_BASE = 'https://partner-api.youla.ru';
    const PROVIDER = 'youla';

    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 {
        $credentials = $this->decodeToken($token);
        $bearer = (string)($credentials['token'] ?? '');
        if ($bearer === '') {
            return ['success' => false, 'error' => 'auth_required'];
        }

        $result = $this->checkAccess($bearer);
        if (empty($result['success'])) {
            return $result;
        }

        return [
            'success' => true,
            'id' => '',
            'name' => 'Youla Partner API',
            'username' => '',
            'profile_url' => '',
            'json' => [],
        ];
    }

    public function syncWebhook(array $channel, string $token, string $webhook_url, string $secret): array {
        $me = $this->getBotInfo($token);
        if (empty($me['success'])) {
            return $me;
        }

        return [
            'success' => true,
            'manual_webhook' => true,
            'bot' => [
                'user_id' => (string)($me['id'] ?? ''),
                'name' => (string)($me['name'] ?? ''),
                'username' => '',
            ],
            'response' => [
                'webhook_url' => $webhook_url,
                'note' => 'Youla Partner API swagger does not expose webhook registration endpoint.',
            ],
            'http_code' => 200,
        ];
    }

    public function parseWebhook(array $payload): array {
        $headers = is_array($payload['_headers'] ?? null) ? $payload['_headers'] : [];
        $event_type = $this->normalizeEventType(trim((string)($headers['ce_type'] ?? $payload['ce_type'] ?? $payload['type'] ?? $payload['event_type'] ?? '')));
        if ($event_type === '' && isset($payload['message'], $payload['chat_id'])) {
            $event_type = 'message.incom';
        }

        $data = $this->eventData($payload);
        if ($event_type !== 'message.incom') {
            $item_id = $this->normalizeProductId((string)($data['product_id'] ?? $data['id'] ?? ''));
            $chat_id = $this->normalizeYoulaId((string)($data['chat_id'] ?? ''));
            $user_id = $this->normalizeYoulaId((string)($data['user_id'] ?? ''));
            $status = $this->systemEventStatus($event_type, $data);
            return [
                'update_type' => $event_type,
                'external_user_id' => '',
                'external_chat_id' => $chat_id,
                'external_message_id' => (string)($data['id'] ?? ''),
                'dedupe_key' => $this->dedupeKey($headers, $data, $event_type),
                'system_event' => true,
                'item_id' => $item_id,
                'item_status' => $status,
                'system_user_id' => $user_id,
                'system_block_type' => $this->limitChars($this->normalizePlainText((string)($data['block_type'] ?? '')), 120),
                'item_context' => $item_id !== '' ? $this->systemItemContext($event_type, $data, $item_id, $status) : [],
            ];
        }

        $external_message_id = (string)($data['id'] ?? '');
        $chat_id = $this->normalizeYoulaId((string)($data['chat_id'] ?? ''));
        $sender_id = $this->normalizeYoulaId((string)($data['sender_id'] ?? ''));
        $recipient_id = $this->normalizeYoulaId((string)($data['recipient_id'] ?? ''));
        $product_id = $this->normalizeProductId((string)($data['product_id'] ?? ''));

        $event = [
            'update_type' => $event_type,
            'text' => trim((string)($data['message'] ?? $data['text'] ?? '')),
            'external_user_id' => $sender_id,
            'external_chat_id' => $chat_id,
            'external_message_id' => $external_message_id,
            'dedupe_key' => $this->dedupeKey($headers, $data, $event_type),
            'recipient_user_id' => $recipient_id,
            'item_id' => $product_id,
            'chat_type' => $product_id !== '' ? 'product' : '',
            'published_at' => (string)($headers['ce_time'] ?? $data['created_at'] ?? $data['published_at'] ?? ''),
            'message_type' => 'text',
            'direction' => 'in',
            'chat_context' => $product_id !== '' ? [
                'context_kind' => 'item',
                'chat_type' => 'product',
                'item_id' => $product_id,
            ] : [],
            'item_context' => $product_id !== '' ? [
                'provider' => self::PROVIDER,
                'id' => $product_id,
                'item_id' => $product_id,
            ] : [],
            'display_name' => 'Покупатель Юлы',
            'username' => '',
            'profile_url' => '',
            'attachments' => $this->extractImages($data),
        ];
        $profile = $this->messageUserContext($data, $sender_id);
        foreach (['display_name', 'username', 'profile_url', 'avatar_url'] as $field) {
            if (!empty($profile[$field])) {
                $event[$field] = $profile[$field];
            }
        }
        return $event;
    }

    public function sendMessage(string $token, string $external_user_id, string $text, array $attachments = [], array $options = []): array {
        $credentials = $this->decodeToken($token);
        $bearer = (string)($credentials['token'] ?? '');
        if ($bearer === '') {
            return ['success' => false, 'error' => 'auth_failed'];
        }
        $text = trim($text);
        $images = $this->outboundImageUrls($attachments);
        $documents = $this->outboundDocumentLinks($attachments);
        $text = $this->limitChars($this->appendDocumentLinks($text, $documents), 1000);
        if (!empty($attachments) && !$images && !$documents) {
            return ['success' => false, 'error' => 'youla_image_url_missing'];
        }
        if ($text === '' && !$images) {
            return ['success' => false, 'error' => 'message_empty'];
        }

        $meta = $this->resolveSendMetadata($external_user_id, $options);
        $seller_id = $this->normalizeYoulaId((string)($meta['seller_user_id'] ?? ''));
        $buyer_id = $this->normalizeYoulaId((string)($meta['buyer_user_id'] ?? ''));
        $product_id = $this->normalizeProductId((string)($meta['product_id'] ?? ''));
        if ($seller_id === '' || $buyer_id === '' || $product_id === '') {
            return ['success' => false, 'error' => 'youla_message_context_missing'];
        }

        $body = [
            'sender_id' => $seller_id,
            'recipient_id' => $buyer_id,
            'product_id' => $product_id,
            'text' => $text,
        ];
        if ($images) {
            $body['images'] = $images;
        }

        $result = $this->apiJson('POST', '/messages', $bearer, $body);
        if (empty($result['success'])) {
            return $result;
        }

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

    private function normalizeEventType(string $event_type): string {
         
         
         
         
        return $event_type === 'message.income' ? 'message.incom' : $event_type;
    }

    private function outboundImageUrls(array $attachments): array {
        $urls = [];
        foreach ($attachments as $attachment) {
            if ($this->attachmentKind($attachment) === 'document') {
                continue;
            }
            $url = trim((string)($attachment['public_url'] ?? ''));
            if ($url === '' || stripos($url, 'https://') !== 0) {
                return [];
            }
            $urls[] = $url;
        }
        return array_slice($urls, 0, 10);
    }

    private function outboundDocumentLinks(array $attachments): array {
        $links = [];
        foreach ($attachments as $attachment) {
            if ($this->attachmentKind($attachment) !== 'document') {
                continue;
            }
            $url = trim((string)($attachment['public_url'] ?? ''));
            if ($url === '' || stripos($url, 'https://') !== 0) {
                return [];
            }
            $links[] = [
                'name' => $this->attachmentDisplayName($attachment),
                'url' => $url,
            ];
        }
        return $links;
    }

    private function appendDocumentLinks(string $text, array $documents): string {
        foreach ($documents as $document) {
            $name = trim((string)($document['name'] ?? ''));
            $url = trim((string)($document['url'] ?? ''));
            if ($url === '') {
                continue;
            }
            $label = $name !== '' ? 'Документ "' . $name . '":' : 'Документ:';
            $text = trim($text . "\n\n" . $label . "\n" . $url);
        }
        return $text;
    }

    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 = \EasyChat\Library\AttachmentPolicy::extensionForMime($mime);
        }
        return \EasyChat\Library\AttachmentPolicy::isDocumentExtension($extension)
            || \EasyChat\Library\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');
    }

    public function getProductInfo(string $token, string $product_id, array $http_options = []): array {
        $credentials = $this->decodeToken($token);
        $bearer = (string)($credentials['token'] ?? '');
        $product_id = $this->normalizeProductId($product_id);
        if ($bearer === '' || $product_id === '') {
            return ['success' => false, 'error' => 'product_lookup_required'];
        }

        $result = $this->apiJson('GET', '/products/' . rawurlencode($product_id), $bearer, null, $http_options);
        if (empty($result['success'])) {
            return $result;
        }

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

    public function getUserInfo(string $token, string $user_id, array $http_options = []): array {
        $credentials = $this->decodeToken($token);
        $bearer = (string)($credentials['token'] ?? '');
        $user_id = $this->normalizeYoulaId($user_id);
        if ($bearer === '' || $user_id === '') {
            return ['success' => false, 'error' => 'user_lookup_required'];
        }

        $result = $this->apiJson('GET', '/users/' . rawurlencode($user_id), $bearer, null, $http_options);
        if (empty($result['success'])) {
            return $result;
        }

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

    private function userContext(array $json, string $fallback_user_id): array {
        $first_name = $this->normalizePlainText((string)($json['first_name'] ?? ''));
        $last_name = $this->normalizePlainText((string)($json['last_name'] ?? ''));
        $name = $this->normalizePlainText((string)($json['name'] ?? ''));
        if ($name === '') {
            $name = $this->normalizePlainText(trim($first_name . ' ' . $last_name));
        }
        $avatar_url = $this->userAvatarUrl($json['avatar_url'] ?? ($json['avatar'] ?? ''));

        return array_filter([
            'external_user_id' => $this->normalizeYoulaId((string)($json['id'] ?? $fallback_user_id)),
            'display_name' => $name,
            'username' => '',
            'profile_url' => '',
            'avatar_url' => $avatar_url,
        ], function ($value) {
            return $value !== '' && $value !== [] && $value !== null;
        });
    }

    private function messageUserContext(array $data, string $sender_id): array {
         
         
         
         
        foreach (['sender', 'buyer', 'user', 'author', 'from', 'customer', 'client', 'counterparty', 'profile', 'sender_profile', 'user_profile'] as $key) {
            if (!is_array($data[$key] ?? null)) {
                continue;
            }
            $candidate = $this->normalizeUserProfilePayload($data[$key]);
            $candidate_id = $this->normalizeYoulaId((string)($candidate['id'] ?? $candidate['user_id'] ?? ''));
            if ($sender_id !== '' && $candidate_id !== '' && !hash_equals($sender_id, $candidate_id)) {
                continue;
            }
            $profile = $this->userContext($candidate, $sender_id);
            if (!empty($profile['display_name']) || !empty($profile['avatar_url'])) {
                return $profile;
            }
        }

        $top_level = [
            'id' => $sender_id,
            'name' => $data['sender_name'] ?? $data['user_name'] ?? $data['buyer_name'] ?? $data['customer_name'] ?? $data['client_name'] ?? '',
            'first_name' => $data['sender_first_name'] ?? $data['first_name'] ?? '',
            'last_name' => $data['sender_last_name'] ?? $data['last_name'] ?? '',
            'avatar_url' => $data['sender_avatar_url'] ?? $data['sender_photo_url'] ?? $data['photo_url'] ?? $data['avatar_url'] ?? $data['avatar'] ?? '',
        ];
        return $this->userContext($this->normalizeUserProfilePayload($top_level), $sender_id);
    }

    private function normalizeUserProfilePayload(array $profile): array {
        if (!isset($profile['avatar_url'])) {
            foreach (['avatar', 'photo_url', 'image_url', 'picture_url', 'userpic_url'] as $avatar_key) {
                if (isset($profile[$avatar_key])) {
                    $profile['avatar_url'] = $profile[$avatar_key];
                    break;
                }
            }
        }
        if (!isset($profile['name'])) {
            foreach (['display_name', 'full_name', 'title'] as $name_key) {
                if (isset($profile[$name_key])) {
                    $profile['name'] = $profile[$name_key];
                    break;
                }
            }
        }
        return $profile;
    }

    private function checkAccess(string $bearer): array {
        $from = gmdate('Y-m-d\TH:i:s\Z', time() - 86400);
        $to = gmdate('Y-m-d\TH:i:s\Z');
        $path = '/products/statistic?product_ids=' . rawurlencode(str_repeat('0', 24))
            . '&from=' . rawurlencode($from)
            . '&to=' . rawurlencode($to);
        $result = $this->apiJson('GET', $path, $bearer, null, [
            'connect_timeout' => 3,
            'timeout' => 8,
        ]);

        if (!empty($result['success'])) {
            return $result;
        }

        $http_code = (int)($result['http_code'] ?? 0);
        if (in_array($http_code, [404, 422], true)) {
            return [
                'success' => true,
                'http_code' => $http_code,
                'json' => [],
                'note' => 'auth_passed_product_statistic_probe',
            ];
        }

        return $result;
    }

    private function resolveSendMetadata(string $external_user_id, array $options): array {
        $metadata = [
            'seller_user_id' => (string)($options['seller_user_id'] ?? ''),
            'buyer_user_id' => (string)($options['buyer_user_id'] ?? ''),
            'product_id' => (string)($options['product_id'] ?? ''),
        ];
        if ($metadata['buyer_user_id'] !== '' && $metadata['product_id'] !== '') {
            return $metadata;
        }
        if (!$this->registry) {
            return $metadata;
        }

        $channel_id = (int)($options['channel_id'] ?? 0);
        if ($channel_id <= 0) {
            return $metadata;
        }

        $model = new \EasyChat\Model\Messenger($this->registry);
        $contact = $model->findContact($channel_id, (string)($options['external_chat_id'] ?? $external_user_id));
        if (!$contact && !empty($options['external_chat_id'])) {
            $contact = $model->findContactByExternalChat($channel_id, (string)$options['external_chat_id']);
        }
        if (!$contact) {
            return $metadata;
        }

        $stored = json_decode((string)($contact['metadata_json'] ?? ''), true);
        $stored = is_array($stored) ? $stored : [];
        foreach (['seller_user_id', 'buyer_user_id', 'product_id'] as $key) {
            if ($metadata[$key] === '' && !empty($stored[$key])) {
                $metadata[$key] = (string)$stored[$key];
            }
        }
        return $metadata;
    }

    private function eventData(array $payload): array {
        foreach (['data', 'payload', 'message'] as $key) {
            if (isset($payload[$key]) && is_array($payload[$key])) {
                return $payload[$key];
            }
        }
        return $payload;
    }

    private function dedupeKey(array $headers, array $data, string $event_type): string {
        $ce_id = trim((string)($headers['ce_id'] ?? ''));
        if ($ce_id !== '') {
            return 'youla:ce:' . $ce_id;
        }
        $message_id = trim((string)($data['id'] ?? ''));
        if ($message_id !== '') {
            return 'youla:' . ($event_type !== '' ? $event_type : 'event') . ':' . $message_id;
        }
        return '';
    }

    private function userAvatarUrl($avatar): string {
        if (is_string($avatar)) {
            $avatar = trim($avatar);
            return $this->isAllowedYoulaImageUrl($avatar) ? $avatar : '';
        }
        if (is_array($avatar)) {
            return $this->firstImageUrl([$avatar]);
        }
        return '';
    }

    private function extractImages(array $data): array {
        $images = is_array($data['images'] ?? null) ? $data['images'] : [];
        $result = [];
        foreach ($images as $image) {
            $url = is_array($image) ? (string)($image['url'] ?? $image['src'] ?? '') : (string)$image;
            $url = trim($url);
            if (!$this->isAllowedYoulaImageUrl($url)) {
                continue;
            }
            $result[] = [
                'type' => 'image',
                'url' => $url,
                'payload_hash' => substr(hash('sha256', $url), 0, 64),
            ];
        }
        return $result;
    }

    private function productContext(array $json, string $fallback_product_id): array {
        $product_id = $this->normalizeProductId((string)($json['id'] ?? $json['product_id'] ?? $fallback_product_id));
        $price = $this->priceString($json['discounted_price'] ?? $json['price'] ?? null);
        $images = is_array($json['images'] ?? null) ? $json['images'] : [];
        $image_url = $this->firstImageUrl($images);

        return array_filter([
            'provider' => self::PROVIDER,
            'id' => $product_id,
            'item_id' => $product_id,
            'title' => (string)($json['name'] ?? $json['title'] ?? ''),
            'url' => $this->absoluteProductUrl((string)($json['short_url'] ?? $json['url_branch'] ?? $json['url'] ?? '')),
            'price_string' => $price,
            'image_url' => $image_url,
            'status_id' => $this->productStatus($json),
            'description' => (string)($json['description'] ?? ''),
            'category' => $this->compactAssoc([
                'category_id' => $json['category_id'] ?? '',
                'subcategory_id' => $json['subcategory_id'] ?? '',
                'category' => $json['category'] ?? '',
                'subcategory' => $json['subcategory'] ?? '',
            ]),
            'location' => is_array($json['location'] ?? null) ? $json['location'] : [],
            'params' => is_array($json['params'] ?? null) ? $json['params'] : [],
        ], function ($value) {
            return $value !== '' && $value !== [] && $value !== null;
        });
    }

    private function responseData(array $json): array {
        return is_array($json['data'] ?? null) ? $json['data'] : $json;
    }

    private function absoluteProductUrl(string $url): string {
        $url = trim($url);
        if ($url === '') {
            return '';
        }
        if (preg_match('/[\x00-\x20\x7f]/', $url) === 1) {
            return '';
        }
        if (preg_match('#^https://#i', $url) === 1 && $this->isAllowedYoulaListingUrl($url)) {
            return $url;
        }
        if (preg_match('#^[a-z][a-z0-9+.-]*:#i', $url) === 1) {
            return '';
        }
        if (strpos($url, '//') === 0 || preg_match('#^/[^\s<>"\\\\]*$#u', $url) !== 1) {
            return '';
        }
        return $url[0] === '/' ? 'https://youla.ru' . $url : '';
    }

    private function productStatus(array $json): string {
        if (!empty($json['is_blocked'])) {
            return trim((string)($json['block_type_text'] ?? 'blocked')) ?: 'blocked';
        }
        if (!empty($json['is_archived'])) {
            return 'archived';
        }
        return '';
    }

    private function systemEventStatus(string $event_type, array $data): string {
        if ($event_type === 'product.archive') {
            return 'archived';
        }
        if ($event_type === 'product.block') {
            return !empty($data['is_hard_blocked']) ? 'hard_blocked' : 'blocked';
        }
        return $this->limitChars($this->normalizePlainText($event_type), 32);
    }

    private function systemItemContext(string $event_type, array $data, string $item_id, string $status): array {
        $context = [
            'provider' => self::PROVIDER,
            'id' => $item_id,
            'item_id' => $item_id,
            'status_id' => $status,
            'event_type' => $this->limitChars($this->normalizePlainText($event_type), 64),
        ];

        if (array_key_exists('is_hard_blocked', $data)) {
            $context['is_hard_blocked'] = !empty($data['is_hard_blocked']) ? 'yes' : 'no';
        }
        if (!empty($data['block_types']) && is_array($data['block_types'])) {
            $context['block_types'] = $this->safeBlockTypes($data['block_types']);
        }

        return array_filter($context, function ($value) {
            return $value !== '' && $value !== [] && $value !== null;
        });
    }

    private function safeBlockTypes(array $block_types): array {
        $result = [];
        foreach ($block_types as $block_type) {
            if (!is_array($block_type)) {
                continue;
            }
            $row = array_filter([
                'block_id' => isset($block_type['block_id']) && is_scalar($block_type['block_id'])
                    ? $this->limitChars((string)$block_type['block_id'], 32)
                    : '',
                'block_description' => $this->limitChars($this->normalizePlainText((string)($block_type['block_description'] ?? '')), 240),
            ], function ($value) {
                return $value !== '';
            });
            if ($row) {
                $result[] = $row;
            }
            if (count($result) >= 8) {
                break;
            }
        }
        return $result;
    }

    private function priceString($price): string {
        if (!is_numeric($price)) {
            return '';
        }
        $rub = ((float)$price) / 100;
        if ($rub <= 0) {
            return '';
        }
        return number_format($rub, 0, '.', ' ') . ' ₽';
    }

    private function firstImageUrl(array $images): string {
        foreach ($images as $image) {
            if (is_string($image) && trim($image) !== '') {
                $url = trim($image);
                if ($this->isAllowedYoulaImageUrl($url)) {
                    return $url;
                }
            }
            if (is_array($image)) {
                foreach (['url', 'src', 'original', 'large'] as $key) {
                    if (!empty($image[$key]) && is_string($image[$key])) {
                        $url = trim($image[$key]);
                        if ($this->isAllowedYoulaImageUrl($url)) {
                            return $url;
                        }
                    }
                }
            }
        }
        return '';
    }

    private function compactAssoc(array $data): array {
        return array_filter($data, function ($value) {
            return $value !== '' && $value !== [] && $value !== null;
        });
    }

    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 = (string)($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($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'] ?? $json['message'] ?? 'api_http_error';
            return [
                'success' => false,
                'error' => $this->safeApiError(is_string($error) && $error !== '' ? $error : 'api_http_error'),
                'http_code' => $http_code,
            ];
        }
        return ['success' => true, 'http_code' => $http_code, 'json' => $json];
    }

    private function decodeToken(string $token): array {
        $token = trim($token);
        if ($token === '') {
            return [];
        }
        $decoded = json_decode($token, true);
        if (is_array($decoded)) {
            $decoded['token'] = $this->normalizeBearerToken((string)($decoded['token'] ?? $decoded['access_token'] ?? ''));
            return $decoded;
        }
        return ['token' => $this->normalizeBearerToken($token)];
    }

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

    private function normalizeYoulaId(string $value): string {
        $value = strtolower(trim($value));
        return preg_match('/^[a-f0-9]{24}$/', $value) === 1 ? $value : '';
    }

    private function normalizeProductId(string $value): string {
        $value = strtolower(trim($value));
        return preg_match('/^[a-f0-9]{24}$/', $value) === 1 ? $value : '';
    }

    private function normalizeBearerToken(string $value): string {
        $value = trim($value);
        $value = preg_replace('/^Bearer\s+/i', '', $value);
        if ($value === '' || preg_match('/[\x00-\x1f\x7f]/', $value) === 1) {
            return '';
        }
        return $value;
    }

    private function isAllowedYoulaListingUrl(string $url): bool {
        return $this->isAllowedHttpsHost($url, ['youla.ru', 'www.youla.ru', 'youla.io', 'youla.link']);
    }

    private function isAllowedYoulaImageUrl(string $url): bool {
        return $this->isAllowedHttpsHost($url, [
            'youla.ru',
            'youla.io',
            'youla.link',
             
            'avatars.mds.yandex.net',
        ]);
    }

    private function isAllowedHttpsHost(string $url, array $allowed_hosts): bool {
        $parts = parse_url(trim($url));
        if (!is_array($parts) || strtolower((string)($parts['scheme'] ?? '')) !== 'https') {
            return false;
        }
        $host = strtolower((string)($parts['host'] ?? ''));
        foreach ($allowed_hosts as $allowed_host) {
            $allowed_host = strtolower($allowed_host);
            if ($host === $allowed_host || substr($host, -strlen('.' . $allowed_host)) === '.' . $allowed_host) {
                return true;
            }
        }
        return false;
    }

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

    private function safeApiError(string $error): string {
        $error = preg_replace('/https?:\/\/\S+/i', '[url]', $error);
        $error = preg_replace('/Bearer\s+[A-Za-z0-9._~+\/=-]+/i', 'Bearer [redacted]', (string)$error);
        $error = preg_replace('/[A-Za-z0-9._~+\/=-]{24,}/', '[redacted]', (string)$error);
        $error = $this->normalizePlainText($error);
        return $this->limitChars($error !== '' ? $error : 'api_http_error', 160);
    }

    private function normalizePlainText(string $value): string {
        $value = preg_replace('#<(script|style)\b[^>]*>.*?</\1>#isu', ' ', $value);
        $value = strip_tags($value);
        $value = preg_replace('/\s+/u', ' ', $value);
        return trim((string)$value);
    }
}
