<?php
namespace EasyChat\Library\Messenger\Marketplace;

use EasyChat\Library\Messenger\InboundService;
use EasyChat\Library\Messenger\MessengerLogger;
use EasyChat\Library\Messenger\OzonProvider;
use EasyChat\Library\Messenger\ProviderRegistry;
use EasyChat\Library\Messenger\YandexMarketProvider;
use EasyChat\Model\Marketplace as MarketplaceModel;
use EasyChat\Model\Messenger as MessengerModel;







class MarketplaceConversationService {
    private $registry;

    public function __construct($registry) {
        $this->registry = $registry;
    }

     
    public function startOptions(): array {
        $result = [];
        $model = new MarketplaceModel($this->registry);
        foreach ([OzonProvider::PROVIDER, YandexMarketProvider::PROVIDER] as $provider) {
            $accounts = [];
            foreach ($model->listAccounts($provider) as $account) {
                if ((string)($account['status'] ?? '') !== 'active') {
                    continue;
                }
                $capabilities = $this->decode((string)($account['capabilities_json'] ?? ''));
                if (($capabilities['account_action_access']['start_conversation'] ?? null) === false) {
                    continue;
                }
                $accounts[] = [
                    'channel_id' => (int)($account['channel_id'] ?? 0),
                    'title' => MarketplaceText::truncate(
                        trim((string)($account['title'] ?? MarketplaceProviderCatalog::get($provider)['title'] ?? $provider)),
                        128
                    ),
                    'external_account_id' => MarketplaceText::truncate(trim((string)($account['external_account_id'] ?? '')), 128),
                    'permission_confirmed' => ($capabilities['account_action_access']['start_conversation'] ?? null) === true,
                ];
            }
            $result[$provider . '_accounts'] = $accounts;
        }
        return $result;
    }

    public function startOzon(array $input, int $manager_id): array {
        $input['provider'] = OzonProvider::PROVIDER;
        return $this->start($input, $manager_id);
    }

    public function start(array $input, int $manager_id): array {
        $channel_id = (int)($input['channel_id'] ?? 0);
        $text = trim((string)($input['text'] ?? ''));
        $request_id = trim((string)($input['request_id'] ?? ''));
        if ($channel_id <= 0) {
            return ['success' => false, 'error' => 'marketplace_account_required'];
        }
        if ($text === '') {
            return ['success' => false, 'error' => 'answer_text_required'];
        }
        if (MarketplaceText::length($text) > 4000) {
            return ['success' => false, 'error' => 'message_text_too_long'];
        }
        if (preg_match('/^[a-z0-9_\-]{12,96}$/i', $request_id) !== 1) {
            return ['success' => false, 'error' => 'marketplace_request_id_invalid'];
        }

        $messenger = new MessengerModel($this->registry);
        $channel = $messenger->getChannelById($channel_id);
        $provider_code = (string)($channel['provider'] ?? '');
        $requested_provider = MarketplaceProviderCatalog::normalize((string)($input['provider'] ?? ''));
        if (!$channel || !in_array($provider_code, [OzonProvider::PROVIDER, YandexMarketProvider::PROVIDER], true)
            || ($requested_provider !== '' && !hash_equals($provider_code, $requested_provider))
            || (string)($channel['status'] ?? '') !== 'active') {
            return ['success' => false, 'error' => 'marketplace_account_inactive'];
        }
        $context = $this->normalizeStartContext($provider_code, $input);
        if (empty($context['success'])) {
            return $context;
        }
        $resource_id = (string)$context['resource_id'];
        $provider_context = (array)$context['provider_context'];
        $capabilities = $this->decode((string)($channel['capabilities_json'] ?? ''));
        if (($capabilities['account_action_access']['start_conversation'] ?? null) === false) {
            return ['success' => false, 'error' => 'marketplace_start_permission_missing'];
        }

        $provider = ProviderRegistry::get($provider_code, $this->registry);
        if (!$provider instanceof MarketplaceProviderInterface) {
            return ['success' => false, 'error' => 'marketplace_provider_unavailable'];
        }
        $provider_capabilities = $provider->marketplaceCapabilities();
        $model = new MarketplaceModel($this->registry);
        $resource_key = $model->conversationStartResourceKey($provider_code . ':' . (string)$context['resource_key']);
        $idempotency_key = hash('sha256', implode('|', [
            (string)$channel_id,
            'start_conversation',
            $request_id,
            (string)$manager_id,
        ]));
        if (!$model->acquireConversationStartLock($channel_id, $resource_key)) {
            return ['success' => false, 'error' => 'marketplace_action_in_progress'];
        }

        $audit = [];
        $external_call_started = false;
        $external_chat_confirmed = false;
        $external_message_confirmed = false;
        $external_chat_id = '';
        $external_message_id = '';
        try {
             
             
            $thread = $provider_code === YandexMarketProvider::PROVIDER
                ? $model->getYandexThreadByContext(
                    $channel_id,
                    (string)($provider_context['context_type'] ?? ''),
                    $resource_id
                )
                : $model->getThreadByOrder($channel_id, MarketplaceStream::BUYER_CHAT, $resource_id);
            if ($thread) {
                return $this->existingConversationResponse($thread, $provider_code);
            }

            $existing = $model->getActionByIdempotencyKey($channel_id, $idempotency_key);
            if ($existing) {
                if ((string)($existing['status'] ?? '') === 'pending') {
                    return $this->quarantinePendingStart($model, $existing, $channel_id);
                }
                return $this->existingResult($existing);
            }
            $resource_action = $model->getConversationStartAction($channel_id, $resource_key);
            if ($resource_action) {
                if ((string)($resource_action['status'] ?? '') === 'pending') {
                    return $this->quarantinePendingStart($model, $resource_action, $channel_id);
                }
                return $this->existingResult($resource_action);
            }

            $credentials = (new MarketplaceCredentialsVault($this->registry))->open(
                $this->decode((string)($channel['credentials_json'] ?? '')),
                is_array($provider_capabilities['secret_fields'] ?? null) ? $provider_capabilities['secret_fields'] : []
            );
            $audit = $model->beginAction([
                'channel_id' => $channel_id,
                'thread_id' => 0,
                'provider' => $provider_code,
                'conversation_kind' => MarketplaceStream::BUYER_CHAT,
                'action' => 'start_conversation',
                'resource_key' => $resource_key,
                'idempotency_key' => $idempotency_key,
                'request' => ['context' => $provider_context, 'text' => $text],
                'manager_id' => $manager_id,
            ]);
            if (!$audit) {
                return ['success' => false, 'error' => 'marketplace_action_audit_failed'];
            }
            if (empty($audit['created'])) {
                return $this->existingResult($audit);
            }

            $external_call_started = true;
            $started = $provider->startMarketplaceConversation($credentials, $provider_context);
            if (empty($started['success'])) {
                return $this->finishFailure($model, $audit, $started);
            }
            $external_chat_id = (string)($started['external_chat_id'] ?? '');
            $external_chat_confirmed = true;
            $token = json_encode($credentials, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            $sent = $provider->sendMessage(
                is_string($token) ? $token : '{}',
                $external_chat_id,
                $text,
                [],
                [
                    'external_chat_id' => $external_chat_id,
                    'external_thread_id' => $external_chat_id,
                    'chat_type' => $provider_code === YandexMarketProvider::PROVIDER ? 'CHAT' : 'buyer_seller',
                ]
            );
            if (empty($sent['success'])) {
                $sent['external_chat_id'] = $external_chat_id;
                 
                 
                 
                 
                $this->wakeChatSync($channel_id);
                $error = !empty($sent['outcome_unknown']) || !empty($sent['retryable'])
                    ? 'marketplace_action_outcome_unknown'
                    : 'marketplace_start_message_failed';
                $model->finishAction((int)$audit['action_id'], 'unknown', [
                    'external_chat_id' => $external_chat_id,
                ], $error);
                return ['success' => false, 'error' => $error, 'action_id' => (int)$audit['action_id']];
            }

            $external_message_confirmed = true;
            $external_message_id = MarketplaceText::truncate(trim((string)($sent['external_message_id'] ?? '')), 128);
            $local_chat_id = $this->materializeConfirmedConversation(
                $model,
                $channel,
                $channel_id,
                $provider_code,
                $external_chat_id,
                $external_message_id,
                $resource_id,
                $provider_context,
                $text
            );
             
             
             
            $this->wakeChatSync($channel_id);
            $response = $this->successResponse($local_chat_id, $external_chat_id, $external_message_id, $provider_code);
            $model->finishAction((int)$audit['action_id'], 'succeeded', $response);
            return $response;
        } catch (\Throwable $e) {
            $logger = new MessengerLogger($this->registry, $provider_code);
            $logger->error('marketplace_start_conversation_failed', [
                'channel_id' => $channel_id,
                'action_id' => (int)($audit['action_id'] ?? 0),
                'external_call_started' => $external_call_started,
                'external_chat_confirmed' => $external_chat_confirmed,
                'external_message_confirmed' => $external_message_confirmed,
                'error_class' => get_class($e),
            ]);
            $action_id = (int)($audit['action_id'] ?? 0);
            if ($external_message_confirmed) {
                $this->wakeChatSync($channel_id);
                $response = $this->successResponse(0, $external_chat_id, $external_message_id, $provider_code);
                if ($action_id > 0) {
                    $model->finishAction($action_id, 'succeeded', $response);
                }
                return $response;
            }

            $outcome_unknown = $external_call_started || $external_chat_confirmed;
            $status = $outcome_unknown ? 'unknown' : 'failed';
            $error = $outcome_unknown
                ? 'marketplace_action_outcome_unknown'
                : 'marketplace_start_failed';
            if ($action_id > 0) {
                $model->finishAction($action_id, $status, [
                    'external_chat_id' => $external_chat_id,
                ], $error);
            }
            if ($outcome_unknown) {
                $this->wakeChatSync($channel_id);
            }
            return ['success' => false, 'error' => $error, 'action_id' => $action_id];
        } finally {
            $model->releaseConversationStartLock($channel_id, $resource_key);
        }
    }

    private function existingResult(array $audit): array {
        $status = (string)($audit['status'] ?? 'pending');
        if ($status === 'succeeded') {
            $response = $this->decode((string)($audit['response_json'] ?? ''));
            return ['success' => true, 'duplicate' => true] + $response;
        }
        if ($status === 'unknown') {
            return ['success' => false, 'error' => 'marketplace_action_outcome_unknown', 'action_id' => (int)$audit['action_id']];
        }
        if ($status === 'failed') {
            return ['success' => false, 'error' => (string)($audit['error'] ?? 'marketplace_start_failed'), 'action_id' => (int)$audit['action_id']];
        }
        return ['success' => false, 'error' => 'marketplace_action_in_progress', 'action_id' => (int)$audit['action_id']];
    }

    private function existingConversationResponse(array $thread, string $provider_code): array {
        $chat_id = (int)($thread['chat_id'] ?? 0);
        $provider_title = (string)(MarketplaceProviderCatalog::get($provider_code)['title'] ?? 'Маркетплейс');
        return [
            'success' => true,
            'duplicate' => true,
            'chat_id' => $chat_id,
            'external_chat_id' => (string)($thread['external_chat_id'] ?? $thread['external_thread_id'] ?? ''),
            'external_message_id' => '',
            'sync_pending' => $chat_id <= 0,
            'message' => $chat_id > 0
                ? 'Диалог по этому заказу или отправлению уже открыт. Повторное сообщение не отправлено.'
                : $provider_title . ' уже создал диалог. EasyChat покажет его после ближайшей сверки.',
        ];
    }

    




    private function quarantinePendingStart(MarketplaceModel $model, array $audit, int $channel_id): array {
        $action_id = (int)($audit['action_id'] ?? 0);
        if ($action_id > 0) {
            $model->finishAction($action_id, 'unknown', [], 'marketplace_action_outcome_unknown');
        }
        $this->wakeChatSync($channel_id);
        return [
            'success' => false,
            'error' => 'marketplace_action_outcome_unknown',
            'action_id' => $action_id,
        ];
    }

    private function finishFailure(MarketplaceModel $model, array $audit, array $result, string $fallback = 'marketplace_start_failed'): array {
        $http_code = (int)($result['http_code'] ?? 0);
        $outcome_unknown = !empty($result['outcome_unknown']) || !empty($result['retryable'])
            || $http_code === 0 || $http_code === 408 || $http_code === 425
            || $http_code === 429 || $http_code >= 500;
        $status = $outcome_unknown ? 'unknown' : 'failed';
        $error = $outcome_unknown
            ? 'marketplace_action_outcome_unknown'
            : (string)($result['error'] ?? $fallback);
        $model->finishAction((int)$audit['action_id'], $status, [
            'external_chat_id' => (string)($result['external_chat_id'] ?? ''),
        ], $error);
        return ['success' => false, 'error' => $error, 'action_id' => (int)$audit['action_id']];
    }

    private function materializeConfirmedConversation(
        MarketplaceModel $model,
        array $channel,
        int $channel_id,
        string $provider_code,
        string $external_chat_id,
        string $external_message_id,
        string $resource_id,
        array $provider_context,
        string $text
    ): int {
        if ($external_message_id === '') return 0;
        try {
            $event = $this->outboundEvent(
                $provider_code,
                $external_chat_id,
                $external_message_id,
                $resource_id,
                $provider_context,
                $text
            );
            $thread = $model->upsertThread([
                'channel_id' => $channel_id,
                'provider' => $provider_code,
                'conversation_kind' => MarketplaceStream::BUYER_CHAT,
                'external_thread_id' => $external_chat_id,
                'external_chat_id' => $external_chat_id,
                'state' => 'opened',
                'order_id' => $resource_id,
                'capabilities' => $event['capabilities'],
                'snapshot' => ['context' => $provider_context, 'started_by_easychat' => true],
                'last_external_at' => $event['published_at'],
            ]);
            $accepted = (new InboundService($this->registry))->acceptMarketplaceEvent($channel, $event);
            $local_chat_id = !empty($accepted['success']) ? (int)($accepted['chat_id'] ?? 0) : 0;
            if (!empty($thread['thread_id']) && $local_chat_id > 0) {
                $model->linkThreadToChat((int)$thread['thread_id'], $local_chat_id);
            }
            return $local_chat_id;
        } catch (\Throwable $e) {
             
             
            (new MessengerLogger($this->registry, $provider_code))->warning(
                'marketplace_start_local_materialization_deferred',
                ['channel_id' => $channel_id, 'error_class' => get_class($e)]
            );
            return 0;
        }
    }

    private function wakeChatSync(int $channel_id): void {
        try {
            (new MarketplaceAccountService($this->registry))->syncNow($channel_id, MarketplaceStream::BUYER_CHAT);
        } catch (\Throwable $e) {
            (new MessengerLogger($this->registry))->warning(
                'marketplace_start_reconciliation_enqueue_failed',
                ['channel_id' => $channel_id, 'error_class' => get_class($e)]
            );
        }
    }

    private function successResponse(
        int $local_chat_id,
        string $external_chat_id,
        string $external_message_id,
        string $provider_code
    ): array {
        $provider_title = (string)(MarketplaceProviderCatalog::get($provider_code)['title'] ?? 'Маркетплейс');
        return [
            'success' => true,
            'chat_id' => $local_chat_id,
            'external_chat_id' => $external_chat_id,
            'external_message_id' => $external_message_id,
            'sync_pending' => $local_chat_id <= 0,
            'message' => $local_chat_id > 0
                ? 'Диалог создан, первое сообщение отправлено.'
                : $provider_title . ' принял сообщение. Диалог появится после ближайшей сверки.',
        ];
    }

    private function outboundEvent(
        string $provider_code,
        string $chat_id,
        string $message_id,
        string $resource_id,
        array $provider_context,
        string $text
    ): array {
        $is_yandex = $provider_code === YandexMarketProvider::PROVIDER;
        return [
            'update_type' => 'message',
            'text' => $text,
            'external_user_id' => $chat_id,
            'external_chat_id' => $chat_id,
            'external_message_id' => $message_id,
            'dedupe_key' => $provider_code . ':chat:' . $chat_id . ':' . $message_id,
            'direction' => 'out',
            'display_name' => $is_yandex ? 'Покупатель Маркета' : 'Покупатель Ozon',
            'attachments' => [],
            'conversation_kind' => MarketplaceStream::BUYER_CHAT,
            'external_thread_id' => $chat_id,
            'thread_state' => 'opened',
            'order_id' => $resource_id,
            'capabilities' => [
                'can_reply' => true,
                'read_only' => false,
                'attachments_out' => true,
                'chat_type' => $is_yandex ? 'CHAT' : 'buyer_seller',
            ],
            'published_at' => date('Y-m-d H:i:s'),
            'metadata' => [
                'marketplace' => true,
                'quiet_import' => false,
                'raw_kind' => $provider_code . '_outbound_start',
            ],
            'snapshot' => ['context' => $provider_context, 'started_by_easychat' => true],
        ];
    }

    private function normalizeStartContext(string $provider_code, array $input): array {
        if ($provider_code === OzonProvider::PROVIDER) {
            $posting_number = trim((string)($input['posting_number'] ?? ''));
            if (MarketplaceText::length($posting_number) > 80
                || preg_match('/^[a-z0-9._\-\/]{3,80}$/i', $posting_number) !== 1) {
                return ['success' => false, 'error' => 'ozon_posting_number_invalid'];
            }
            return [
                'success' => true,
                'resource_id' => $posting_number,
                'resource_key' => 'posting:' . $posting_number,
                'provider_context' => ['posting_number' => $posting_number],
            ];
        }
        if ($provider_code === YandexMarketProvider::PROVIDER) {
            $context_type = strtoupper(trim((string)($input['context_type'] ?? 'ORDER')));
            $context_id = trim((string)($input['context_id'] ?? ''));
            if (!in_array($context_type, ['ORDER', 'RETURN'], true)
                || preg_match('/^[1-9][0-9]{0,18}$/', $context_id) !== 1) {
                return ['success' => false, 'error' => 'yandex_market_context_invalid'];
            }
            return [
                'success' => true,
                'resource_id' => $context_id,
                'resource_key' => strtolower($context_type) . ':' . $context_id,
                'provider_context' => [
                    'context_type' => $context_type,
                    'context_id' => $context_id,
                ],
            ];
        }
        return ['success' => false, 'error' => 'marketplace_provider_unavailable'];
    }

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