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

use EasyChat\Library\Http\ExternalHttpClient;





class MarketplaceHttpTransport {
    private const MAX_RESPONSE_BYTES = 12 * 1024 * 1024;

    private $http;
    private $allowed_hosts;
    private $trust_profile;

    public function __construct(ExternalHttpClient $http, array $allowed_hosts, string $trust_profile) {
        $this->http = $http;
        $this->allowed_hosts = array_fill_keys(array_map('strtolower', $allowed_hosts), true);
        $this->trust_profile = $trust_profile;
    }

    public function json(string $method, string $url, array $headers = [], $body = null, int $timeout = 12): array {
        if (!$this->allowedUrl($url)) {
            return ['success' => false, 'error' => 'marketplace_host_not_allowed', 'http_code' => 0];
        }
        $request = [
            'headers' => $headers,
            'connect_timeout' => 4,
            'timeout' => max(5, min(20, $timeout)),
            'follow_location' => false,
            'trust_profile' => $this->trust_profile,
            'proxy_profile' => 'messenger_' . $this->trust_profile,
        ];
        if ($body !== null) {
            $request['body'] = is_string($body)
                ? $body
                : json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        }
        $result = $this->boundedRequest($method, $url, $request);
        return $this->normalize($result);
    }

    public function multipart(string $url, array $headers, array $fields, int $timeout = 20): array {
        if (!$this->allowedUrl($url)) {
            return ['success' => false, 'error' => 'marketplace_host_not_allowed', 'http_code' => 0];
        }
        $result = $this->boundedRequest('POST', $url, [
            'headers' => $headers,
            'post_fields' => $fields,
            'connect_timeout' => 4,
            'timeout' => max(10, min(30, $timeout)),
            'follow_location' => false,
            'trust_profile' => $this->trust_profile,
            'proxy_profile' => 'messenger_' . $this->trust_profile,
        ]);
        return $this->normalize($result);
    }

    private function normalize(array $result): array {
        if (!empty($result['response_too_large'])) {
            return [
                'success' => false,
                'http_code' => (int)($result['http_code'] ?? 0),
                'error' => 'marketplace_response_too_large',
                'retryable' => false,
                'json' => [],
            ];
        }
        $http_code = (int)($result['http_code'] ?? 0);
        $raw = $result['raw'] ?? false;
        $json = is_string($raw) && trim($raw) !== '' ? json_decode($raw, true) : [];
        $json = is_array($json) ? $json : [];
        $transport_ok = !empty($result['transport_ok']);
        $success = $transport_ok && $http_code >= 200 && $http_code < 300;
        $api_error = $success ? $this->apiError($json) : '';
        if ($api_error !== '') {
            $failure = [
                'success' => false,
                'http_code' => $http_code,
                'error' => $api_error,
                'retryable' => $this->apiErrorIsRetryable($api_error),
                'json' => $json,
            ];
            return $this->withRetryAfter($failure, $result);
        }
        if ($success) {
            return [
                'success' => true,
                'http_code' => $http_code,
                'json' => $json,
                'raw' => is_string($raw) ? $raw : '',
                'content_type' => (string)($result['content_type'] ?? ''),
            ];
        }

        $failure = [
            'success' => false,
            'http_code' => $http_code,
            'error' => $this->errorCode($http_code, $result, $json),
            'retryable' => $http_code === 0 || $http_code === 408 || $http_code === 420 || $http_code === 425 || $http_code === 429 || $http_code >= 500,
            'json' => $json,
        ];
        return $this->withRetryAfter($failure, $result);
    }

    private function errorCode(int $http_code, array $transport, array $json): string {
        if ($http_code === 401) return 'auth_failed';
        if ($http_code === 402) return 'subscription_required';
        if ($http_code === 403) return 'permission_denied';
        if ($http_code === 404) return 'endpoint_or_resource_not_found';
        if ($http_code === 409) return 'marketplace_conflict';
        if ($http_code === 420) return 'rate_limited';
        if ($http_code === 429) return 'rate_limited';
        if ($http_code >= 500) return 'marketplace_unavailable';
        if (empty($transport['transport_ok'])) return 'transport_failed';

        $code = (string)($json['code'] ?? $json['error']['code'] ?? $json['errorCode'] ?? '');
        $code = strtolower(preg_replace('/[^a-z0-9_\-]/i', '_', $code));
        return $code !== '' ? 'marketplace_' . $code : 'marketplace_request_failed';
    }

    




    private function apiError(array $json): string {
        $has_error = ($json['error'] ?? false) === true
            || strtoupper((string)($json['status'] ?? '')) === 'ERROR';
        $errors = $json['errors'] ?? [];
        if (!$has_error && (is_array($errors) ? count($errors) === 0 : trim((string)$errors) === '')) {
            return '';
        }

        $code = '';
        if (is_array($errors) && isset($errors[0]) && is_array($errors[0])) {
            $code = (string)($errors[0]['code'] ?? $errors[0]['errorCode'] ?? $errors[0]['title'] ?? '');
        }
        if ($code === '') {
            $code = (string)($json['code'] ?? $json['errorCode'] ?? (is_string($json['error'] ?? null) ? $json['error'] : ''));
        }
        $code = strtolower(trim((string)preg_replace('/[^a-z0-9_\-]/i', '_', $code), '_'));
        return $code !== '' ? 'marketplace_' . $code : 'marketplace_api_error';
    }

    private function apiErrorIsRetryable(string $error): bool {
        return preg_match('/(?:rate|limit|timeout|tempor|unavailable|overload|try_again)/i', $error) === 1;
    }

    



    private function boundedRequest(string $method, string $url, array $request): array {
        $buffer = '';
        $overflow = false;
        $response_headers = [];
        $request['write_function'] = function ($ch, string $chunk) use (&$buffer, &$overflow): int {
            $length = strlen($chunk);
            if (strlen($buffer) + $length > self::MAX_RESPONSE_BYTES) {
                $overflow = true;
                return 0;
            }
            $buffer .= $chunk;
            return $length;
        };
         
         
         
        $request['curl_options'][CURLOPT_HEADERFUNCTION] = function ($ch, string $line) use (&$response_headers): int {
            $length = strlen($line);
            $parts = explode(':', $line, 2);
            if (count($parts) !== 2) {
                return $length;
            }
            $name = strtolower(trim($parts[0]));
            if (!in_array($name, ['retry-after', 'x-ratelimit-retry'], true)) {
                return $length;
            }
            $value = trim($parts[1]);
            if (preg_match('/^\d{1,6}$/', $value) === 1) {
                $response_headers[$name] = $value;
            }
            return $length;
        };
        $result = $this->http->request($method, $url, $request);
        $result['safe_response_headers'] = $response_headers;
        if ($overflow) {
            $result['response_too_large'] = true;
            $result['transport_ok'] = false;
        } else {
             
             
            $result['raw'] = $buffer;
        }
        return $result;
    }

    private function withRetryAfter(array $failure, array $transport): array {
        $headers = is_array($transport['safe_response_headers'] ?? null)
            ? $transport['safe_response_headers']
            : [];
        $value = $headers['x-ratelimit-retry'] ?? $headers['retry-after'] ?? '';
        if (is_string($value) && preg_match('/^\d{1,6}$/', $value) === 1) {
            $failure['retry_after_seconds'] = max(1, min(21600, (int)$value));
        }
        return $failure;
    }

    private function allowedUrl(string $url): bool {
        $parts = parse_url($url);
        return is_array($parts)
            && strtolower((string)($parts['scheme'] ?? '')) === 'https'
            && !isset($parts['user'])
            && !isset($parts['pass'])
            && (!isset($parts['port']) || (int)$parts['port'] === 443)
            && isset($this->allowed_hosts[strtolower((string)($parts['host'] ?? ''))]);
    }
}
