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

use EasyChat\Library\Messenger\ProviderRegistry;
use EasyChat\Model\Marketplace as MarketplaceModel;
use EasyChat\Model\Messenger as MessengerModel;








class MarketplaceActionService {
    private $registry;

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

    public function execute(int $chat_id, string $action, array $input, int $manager_id): array {
        $action = strtolower(preg_replace('/[^a-z0-9_\-]/i', '', $action));
        $raw_text = trim((string)($input['text'] ?? ''));
        if (MarketplaceText::length($raw_text) > 5000) {
            return ['success' => false, 'error' => 'message_text_too_long'];
        }
        $request_id = trim((string)($input['request_id'] ?? ''));
        if (preg_match('/^[a-z0-9_\-]{12,96}$/i', $request_id) !== 1) {
            return ['success' => false, 'error' => 'marketplace_request_id_invalid'];
        }

        $model = new MarketplaceModel($this->registry);
        $thread = $model->getThreadByChat($chat_id);
        if (!$thread) {
            return ['success' => false, 'error' => 'marketplace_thread_not_found'];
        }

        $idempotency_key = hash('sha256', implode('|', [
            (string)$thread['channel_id'], (string)$thread['thread_id'], $action,
            $request_id, (string)$manager_id,
        ]));
        $existing = $model->getActionByIdempotencyKey((int)$thread['channel_id'], $idempotency_key);
        if ($existing && (string)($existing['status'] ?? '') !== 'pending') {
            return $this->existingResult($existing);
        }
        if (!$model->acquireActionLock((int)$thread['thread_id'])) {
            return ['success' => false, 'error' => 'marketplace_action_in_progress'];
        }

        $audit = [];
        $external_call_started = false;
        $external_outcome = 'not_started';
        $confirmed_result = [];
        try {
             
             
             
            $thread = $model->getThreadById((int)$thread['thread_id']);
            if (!$thread || (int)($thread['chat_id'] ?? 0) !== $chat_id) {
                return ['success' => false, 'error' => 'marketplace_thread_not_found'];
            }
            $existing = $model->getActionByIdempotencyKey((int)$thread['channel_id'], $idempotency_key);
            if ($existing) {
                if ((string)($existing['status'] ?? '') === 'pending') {
                    return $this->quarantineOrphanedPending($model, $thread, $existing);
                }
                return $this->existingResult($existing);
            }
            $unreconciled = $model->getUnreconciledAction(
                (int)$thread['thread_id'],
                max(0, (int)($thread['sync_revision'] ?? 0))
            );
            if ($unreconciled) {
                if ((string)($unreconciled['status'] ?? '') === 'pending') {
                    return $this->quarantineOrphanedPending($model, $thread, $unreconciled);
                }
                return [
                    'success' => false,
                    'error' => (string)($unreconciled['status'] ?? '') === 'unknown'
                        ? 'marketplace_action_outcome_unknown'
                        : 'marketplace_action_in_progress',
                    'action_id' => (int)($unreconciled['action_id'] ?? 0),
                ];
            }

            $capabilities = $this->decode((string)($thread['capabilities_json'] ?? ''));
            $allowed = array_values(array_filter(array_map('strval', (array)($capabilities['actions'] ?? []))));
            if ($action === '' || !in_array($action, $allowed, true)) {
                return ['success' => false, 'error' => 'marketplace_action_not_allowed'];
            }
            if ($action === 'review_answer' && array_key_exists('review_commentable', $capabilities) && empty($capabilities['review_commentable'])) {
                return ['success' => false, 'error' => 'review_comment_not_allowed'];
            }

            $channel = (new MessengerModel($this->registry))->getChannelById((int)$thread['channel_id']);
            if (!$channel || (string)($channel['status'] ?? '') !== 'active') {
                return ['success' => false, 'error' => 'marketplace_channel_inactive'];
            }
            $provider_code = strtolower((string)$thread['provider']);
            $provider = ProviderRegistry::get($provider_code, $this->registry);
            if (!$provider instanceof MarketplaceProviderInterface) {
                return ['success' => false, 'error' => 'marketplace_provider_unavailable'];
            }

            $provider_capabilities = $provider->marketplaceCapabilities();
            $stored = $this->decode((string)($channel['credentials_json'] ?? ''));
            $credentials = (new MarketplaceCredentialsVault($this->registry))->open(
                $stored,
                is_array($provider_capabilities['secret_fields'] ?? null) ? $provider_capabilities['secret_fields'] : []
            );
            $snapshot = $this->decode((string)($thread['snapshot_json'] ?? ''));
            $input['text'] = $raw_text;
            $context = $this->serverContext($thread, $capabilities, $snapshot, $input, $action);
            if (!empty($context['_validation_error'])) {
                return ['success' => false, 'error' => (string)$context['_validation_error']];
            }
            $audit = $model->beginAction([
                'channel_id' => (int)$thread['channel_id'],
                'thread_id' => (int)$thread['thread_id'],
                'provider' => $provider_code,
                'conversation_kind' => (string)$thread['conversation_kind'],
                'action' => $action,
                'idempotency_key' => $idempotency_key,
                'thread_revision' => max(0, (int)($thread['sync_revision'] ?? 0)),
                'request' => $this->auditRequest($context),
                '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;
            $result = $provider->performMarketplaceAction($action, $credentials, $context);
            $confirmed_result = $result;
            if (empty($result['success'])) {
                $http_code = (int)($result['http_code'] ?? 0);
                $outcome_unknown = !empty($result['retryable'])
                    || $http_code === 0 || $http_code === 408 || $http_code === 425
                    || $http_code === 429 || $http_code >= 500;
                if ($outcome_unknown) {
                    $external_outcome = 'unknown';
                     
                     
                     
                    $model->finishAction((int)$audit['action_id'], 'unknown', [], 'marketplace_action_outcome_unknown');
                    $model->wakeStream((int)$thread['channel_id'], (string)$thread['conversation_kind']);
                    return [
                        'success' => false,
                        'error' => 'marketplace_action_outcome_unknown',
                        'action_id' => (int)$audit['action_id'],
                    ];
                }
                $external_outcome = 'failed';
                $model->finishAction((int)$audit['action_id'], 'failed', [], (string)($result['error'] ?? 'marketplace_action_failed'));
                return $result + ['action_id' => (int)$audit['action_id']];
            }
            $external_outcome = 'succeeded';
            if (
                $provider_code === 'wildberries'
                && in_array($action, ['question_answer', 'review_answer', 'review_edit'], true)
            ) {
                $projection = (new MarketplaceAnswerProjector($this->registry))->project(
                    $thread,
                    $action,
                    (string)($context['text'] ?? ''),
                    $manager_id
                );
                if (empty($projection['success'])) {
                     
                     
                     
                    throw new \RuntimeException('marketplace_answer_projection_failed');
                }
            }
            $model->finishAction((int)$audit['action_id'], 'succeeded', is_array($result['response'] ?? null) ? $result['response'] : []);
            $model->applySuccessfulAction((int)$thread['thread_id'], $action);
            $model->wakeStream((int)$thread['channel_id'], (string)$thread['conversation_kind']);
            return $result + ['action_id' => (int)$audit['action_id']];
        } catch (\Throwable $e) {
             
             
             
            $action_id = (int)($audit['action_id'] ?? 0);
            if ($external_outcome === 'succeeded') {
                 
                 
                 
                try {
                    $model->finishAction(
                        $action_id,
                        'succeeded',
                        is_array($confirmed_result['response'] ?? null) ? $confirmed_result['response'] : []
                    );
                    $model->wakeStream((int)($thread['channel_id'] ?? 0), (string)($thread['conversation_kind'] ?? ''));
                } catch (\Throwable $ignored) {
                     
                     
                }
                return [
                    'success' => true,
                    'sync_pending' => true,
                    'action_id' => $action_id,
                ];
            }
            if ($external_outcome === 'failed') {
                 
                 
                return $confirmed_result + ['action_id' => $action_id];
            }
            $error = $external_call_started
                ? 'marketplace_action_outcome_unknown'
                : 'marketplace_action_failed';
            if ($action_id > 0) {
                try {
                    $model->finishAction(
                        $action_id,
                        $external_call_started ? 'unknown' : 'failed',
                        [],
                        $error
                    );
                } catch (\Throwable $ignored) {
                     
                     
                }
            }
            if ($external_call_started) {
                try {
                    $model->wakeStream((int)($thread['channel_id'] ?? 0), (string)($thread['conversation_kind'] ?? ''));
                } catch (\Throwable $ignored) {
                     
                }
            }
            return ['success' => false, 'error' => $error, 'action_id' => $action_id];
        } finally {
            $model->releaseActionLock((int)($thread['thread_id'] ?? 0));
        }
    }

    private function serverContext(array $thread, array $capabilities, array $snapshot, array $input, string $action): array {
        $text = MarketplaceText::truncate(trim((string)($input['text'] ?? '')), 5000);
        $context = [
            'external_thread_id' => (string)$thread['external_thread_id'],
            'text' => $text,
            'allowed_actions' => array_values(array_filter(array_map('strval', (array)($capabilities['allowed_claim_actions'] ?? [])))),
            'allowed_action_ids' => [],
        ];
        $targets = is_array($capabilities['action_targets'] ?? null)
            ? $capabilities['action_targets']
            : [];
        if (is_array($targets[$action] ?? null)) {
             
             
            $context['action_target'] = $targets[$action];
        }
        $item = $this->findArray($snapshot, ['item_context', 'productDetails', 'product']);
        $context['sku'] = (string)($item['sku'] ?? $snapshot['sku'] ?? '');
        $context['parent_comment_id'] = (string)($snapshot['parent_comment_id'] ?? '');

        $provider_payload = is_array($snapshot['provider_payload'] ?? null) ? $snapshot['provider_payload'] : $snapshot;
        $workflow = is_array($capabilities['workflow_actions'] ?? null)
            ? $capabilities['workflow_actions']
            : (is_array($snapshot['workflow_actions'] ?? null)
                ? $snapshot['workflow_actions']
                : (is_array($provider_payload['available_actions'] ?? null)
                    ? $provider_payload['available_actions']
                    : (is_array($provider_payload['actions'] ?? null) ? $provider_payload['actions'] : [])));
        foreach ($workflow as $entry) {
            if (is_array($entry) && (int)($entry['id'] ?? 0) > 0) {
                $context['allowed_action_ids'][] = (int)$entry['id'];
            }
        }
        $requested_action_id = (int)($input['action_id'] ?? 0);
        if ($requested_action_id > 0) {
            $context['action_id'] = $requested_action_id;
        }
        $claim_action = preg_replace('/[^a-z0-9_\-]/i', '', (string)($input['claim_action'] ?? ''));
        if ($claim_action !== '') {
            $context['claim_action'] = $claim_action;
        }
        $rejection_reasons = is_array($capabilities['return_rejection_reasons'] ?? null)
            ? $capabilities['return_rejection_reasons']
            : [];
        $allowed_reasons = [];
        foreach ($rejection_reasons as $reason) {
            if (!is_array($reason) || (int)($reason['id'] ?? 0) <= 0) continue;
            $allowed_reasons[(int)$reason['id']] = !empty($reason['requires_comment']);
        }
        if (isset($input['rejection_reason_id']) && $input['rejection_reason_id'] !== '') {
            $reason_id = (int)$input['rejection_reason_id'];
            if ($reason_id <= 0 || !array_key_exists($reason_id, $allowed_reasons)) {
                $context['_validation_error'] = 'return_rejection_reason_not_allowed';
                return $context;
            }
            if ($allowed_reasons[$reason_id] && MarketplaceText::length($text) < 2) {
                $context['_validation_error'] = 'return_comment_required';
                return $context;
            }
            $context['rejection_reason_id'] = $reason_id;
        }
        foreach (['compensation_amount', 'return_for_back_way'] as $field) {
            if (!isset($input[$field]) || $input[$field] === '') continue;
            if (!is_numeric($input[$field])) {
                $context['_validation_error'] = 'return_amount_invalid';
                return $context;
            }
            $amount = round((float)$input[$field], 2);
            if ($amount < 0 || $amount > 100000000) {
                $context['_validation_error'] = 'return_amount_invalid';
                return $context;
            }
            $context[$field] = $amount;
        }
        return $context;
    }

    




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

    private function auditRequest(array $context): array {
        return array_intersect_key($context, array_flip([
            'external_thread_id', 'text', 'sku', 'action_id', 'claim_action',
            'compensation_amount', 'rejection_reason_id', 'return_for_back_way',
            'action_target',
        ]));
    }

    private function existingResult(array $audit): array {
        $status = (string)($audit['status'] ?? 'pending');
        if ($status === 'succeeded' || $status === 'failed') {
            return [
                'success' => $status === 'succeeded',
                'replayed' => true,
                'action_id' => (int)($audit['action_id'] ?? 0),
                'error' => $status === 'failed'
                    ? (string)($audit['error'] ?? 'marketplace_action_failed')
                    : '',
            ];
        }
        return [
            'success' => false,
            'replayed' => true,
            'action_id' => (int)($audit['action_id'] ?? 0),
            'error' => $status === 'unknown'
                ? 'marketplace_action_outcome_unknown'
                : 'marketplace_action_in_progress',
        ];
    }

    private function findArray(array $source, array $keys): array {
        foreach ($keys as $key) {
            if (is_array($source[$key] ?? null)) {
                return $source[$key];
            }
        }
        foreach ($source as $value) {
            if (!is_array($value)) {
                continue;
            }
            $found = $this->findArray($value, $keys);
            if ($found) {
                return $found;
            }
        }
        return [];
    }

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