<?php

declare(strict_types=1);

final class MyronMarketplaceExtensionPackageClient
{
    public function __construct(
        private readonly string $baseUrl = '',
        private readonly string $authToken = '',
        private readonly int $timeoutSeconds = 8,
    ) {
    }

    public function list(array $filters = []): array
    {
        $fixture = trim((string) getenv('MYRON_MARKETPLACE_EXTENSION_PACKAGES_FIXTURE'));
        if ($fixture !== '' && is_file($fixture)) {
            return $this->decodeListPayload((string) file_get_contents($fixture), 'fixture');
        }

        $inlineJson = trim((string) getenv('MYRON_MARKETPLACE_EXTENSION_PACKAGES_JSON'));
        if ($inlineJson !== '') {
            return $this->decodeListPayload($inlineJson, 'inline');
        }

        if ($this->outboundDisabled()) {
            return $this->emptyListFailure('outbound_disabled');
        }

        $url = $this->listUrl($filters);
        if ($url === '') {
            return $this->emptyListFailure('marketplace_url_unavailable');
        }

        $json = $this->httpGet($url);
        if ($json === '') {
            return $this->emptyListFailure('extension_marketplace_unreachable');
        }

        return $this->decodeListPayload($json, 'remote');
    }

    public function download(array $candidate, string $destinationPath): array
    {
        $fixturePath = trim((string) ($candidate['downloadPath'] ?? $candidate['fixtureDownloadPath'] ?? ''));
        if ($fixturePath !== '') {
            if (!is_file($fixturePath)) {
                throw new RuntimeException('Extension package fixture download is unavailable.');
            }
            $this->ensureDirectory(dirname($destinationPath));
            if (!copy($fixturePath, $destinationPath)) {
                throw new RuntimeException('Extension package fixture download could not be copied.');
            }
            return $this->downloadResult($destinationPath, $candidate);
        }

        if ($this->outboundDisabled()) {
            throw new RuntimeException('Extension package download is unavailable while outbound requests are disabled.');
        }

        $url = trim((string) ($candidate['downloadUrl'] ?? ''));
        if ($url === '') {
            $packageVersionId = trim((string) ($candidate['packageVersionId'] ?? ''));
            if ($packageVersionId !== '') {
                $url = $this->resolvedBaseUrl() . '/api/v2/extension-package-download.php?' . http_build_query(['packageVersionId' => $packageVersionId]);
            }
        }
        if ($url === '' || !preg_match('#^https?://#i', $url)) {
            throw new RuntimeException('Extension package download URL is unavailable.');
        }

        $bytes = $this->httpGet($url);
        if ($bytes === '') {
            throw new RuntimeException('Extension package download failed.');
        }

        $this->ensureDirectory(dirname($destinationPath));
        if (file_put_contents($destinationPath, $bytes) === false) {
            throw new RuntimeException('Extension package download could not be written.');
        }

        return $this->downloadResult($destinationPath, $candidate);
    }

    private function downloadResult(string $path, array $candidate): array
    {
        $hash = hash_file('sha256', $path);
        if (!is_string($hash)) {
            throw new RuntimeException('Extension package download hash could not be calculated.');
        }

        $expected = strtolower(trim((string) ($candidate['signedArtifactSha256'] ?? ($candidate['signing']['signedArtifactSha256'] ?? ''))));
        if ($expected !== '' && !hash_equals($expected, $hash)) {
            throw new RuntimeException('Extension package download checksum mismatch.');
        }

        return [
            'path' => $path,
            'sha256' => $hash,
            'sizeBytes' => is_file($path) ? (int) filesize($path) : 0,
        ];
    }

    private function listUrl(array $filters): string
    {
        $base = $this->resolvedBaseUrl();
        if ($base === '') {
            return '';
        }

        $query = [];
        foreach (['packageId', 'q', 'sort', 'capability', 'compatibility', 'publisherId', 'namespace', 'category', 'collection'] as $key) {
            $value = trim((string) ($filters[$key] ?? ''));
            if ($value !== '') {
                $query[$key] = $value;
            }
        }

        return $base . '/api/v2/extension-packages.php' . ($query === [] ? '' : '?' . http_build_query($query));
    }

    private function resolvedBaseUrl(): string
    {
        $url = trim($this->baseUrl);
        if ($url === '') {
            $url = trim((string) getenv('MYRON_MARKETPLACE_URL'));
        }
        if ($url === '') {
            $url = 'https://market.myroncms.com';
        }
        if (!preg_match('#^https?://#i', $url)) {
            return '';
        }

        return rtrim($url, '/');
    }

    private function httpGet(string $url): string
    {
        $headers = ["Accept: application/json"];
        $token = $this->resolvedAuthToken();
        if ($token !== '') {
            $headers[] = 'Authorization: Bearer ' . $token;
            $headers[] = 'X-Myron-Marketplace-Actor-Subject: app:extension-install';
        }

        $context = stream_context_create([
            'http' => [
                'method' => 'GET',
                'timeout' => max(1, $this->timeoutSeconds),
                'header' => implode("\r\n", $headers) . "\r\n",
            ],
        ]);
        $contents = @file_get_contents($url, false, $context);

        return is_string($contents) ? $contents : '';
    }

    private function resolvedAuthToken(): string
    {
        $token = trim($this->authToken);
        if ($token === '') {
            $token = trim((string) getenv('MYRON_MARKETPLACE_EXTENSION_INSTALL_TOKEN'));
        }

        return $token;
    }

    private function decodeListPayload(string $json, string $source): array
    {
        $payload = json_decode($json, true);
        if (!is_array($payload)) {
            return $this->emptyListFailure('extension_marketplace_json_invalid', $source);
        }

        $rawItems = is_array($payload['items'] ?? null) ? $payload['items'] : [];
        $items = [];
        foreach ($rawItems as $item) {
            if (!is_array($item)) {
                continue;
            }
            $packageVersionId = trim((string) ($item['packageVersionId'] ?? ''));
            $packageId = trim((string) ($item['packageId'] ?? ''));
            $displayName = trim((string) ($item['displayName'] ?? $packageId));
            if ($packageVersionId === '' || $packageId === '' || $displayName === '') {
                continue;
            }
            if (trim((string) ($item['targetType'] ?? '')) !== 'extension') {
                continue;
            }
            if (($item['installable'] ?? false) !== true) {
                continue;
            }

            $items[] = $item + [
                'packageVersionId' => $packageVersionId,
                'packageId' => $packageId,
                'displayName' => $displayName,
            ];
        }

        return [
            'ok' => true,
            'items' => $items,
            'source' => $source,
            'error' => '',
            'boundary' => (string) ($payload['boundary'] ?? ''),
        ];
    }

    private function emptyListFailure(string $error, string $source = ''): array
    {
        return ['ok' => false, 'items' => [], 'source' => $source, 'error' => $error, 'boundary' => ''];
    }

    private function outboundDisabled(): bool
    {
        $raw = strtolower(trim((string) getenv('MYRON_NO_OUTBOUND')));
        return in_array($raw, ['1', 'true', 'yes', 'on'], true);
    }

    private function ensureDirectory(string $path): void
    {
        if (!is_dir($path) && !mkdir($path, 0775, true) && !is_dir($path)) {
            throw new RuntimeException('Unable to create Extension package download directory.');
        }
    }
}
