<?php
declare(strict_types=1);

namespace Celena\Core\Plugin;

use RuntimeException;

/**
 * Клиент REST API маркетплейса автора. Минимальная реализация.
 *
 * API (ожидается на $baseUrl):
 *   GET  /v1/list?type=plugin|theme&page=
 *   GET  /v1/item/{slug}
 *   POST /v1/install   body: {slug, version, license_key, domain}
 *        returns: {ok:true, zip_url, sha256, expires_at}
 *   GET  /v1/updates?installed=...&license_key=...&domain=...
 *
 * Если сервер недоступен — бросает RuntimeException. UI обрабатывает.
 */
final class Marketplace
{
    public function __construct(
        private readonly string $baseUrl,
        private readonly string $licenseKey = '',
        private readonly string $domain = '',
    ) {
    }

    public function list(string $type = 'plugin', int $page = 1, string $query = '', string $category = ''): array
    {
        $url = $this->baseUrl . '/v1/list?' . http_build_query([
            'type' => $type, 'page' => $page, 'q' => $query, 'category' => $category,
        ]);
        return $this->requestJson('GET', $url);
    }

    public function item(string $slug): array
    {
        return $this->requestJson('GET', $this->baseUrl . '/v1/item/' . rawurlencode($slug));
    }

    public function installUrl(string $slug, string $version = 'latest', string $licenseKey = ''): array
    {
        return $this->requestJson('POST', $this->baseUrl . '/v1/install', [
            'slug' => $slug, 'version' => $version,
            'license_key' => $licenseKey !== '' ? $licenseKey : $this->licenseKey,
            'domain' => $this->domain,
        ]);
    }

    /**
     * @param array<string, string> $installed slug => version
     * @param array<string, string> $licenses  slug => license_key (для платных пакетов)
     */
    public function updates(array $installed, array $licenses = []): array
    {
        return $this->requestJson('POST', $this->baseUrl . '/v1/updates', [
            'installed' => $installed,
            'licenses' => $licenses,
            'license_key' => $this->licenseKey,
            'domain' => $this->domain,
        ]);
    }

    public function download(string $url, string $targetFile, string $expectedSha256 = ''): void
    {
        $ch = curl_init($url);
        $fp = fopen($targetFile, 'w');
        if ($fp === false) {
            throw new RuntimeException('Cannot open target file: ' . $targetFile);
        }
        curl_setopt_array($ch, [
            CURLOPT_FILE => $fp,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_TIMEOUT => 120,
        ]);
        curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $err = curl_error($ch);
        curl_close($ch);
        fclose($fp);
        if ($status >= 400 || $err !== '') {
            @unlink($targetFile);
            throw new RuntimeException("Download failed ($status): $err");
        }
        if ($expectedSha256 !== '' && hash_file('sha256', $targetFile) !== $expectedSha256) {
            @unlink($targetFile);
            throw new RuntimeException('SHA256 mismatch — archive is corrupted or tampered.');
        }
    }

    private function requestJson(string $method, string $url, ?array $body = null): array
    {
        $ch = curl_init($url);
        $opts = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_HTTPHEADER => ['Accept: application/json', 'User-Agent: Celena-CMS'],
            CURLOPT_FOLLOWLOCATION => true,
        ];
        if ($method === 'POST') {
            $opts[CURLOPT_POST] = true;
            $opts[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json';
            $opts[CURLOPT_POSTFIELDS] = json_encode($body ?: (object) [], JSON_UNESCAPED_UNICODE);
        }
        curl_setopt_array($ch, $opts);
        $resp = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $err = curl_error($ch);
        curl_close($ch);
        if ($resp === false) {
            throw new RuntimeException('Marketplace request failed: ' . $err);
        }
        $data = json_decode((string) $resp, true);
        if ($status >= 400 || !is_array($data)) {
            $msg = is_array($data) ? ($data['error'] ?? 'unknown') : substr((string) $resp, 0, 200);
            throw new RuntimeException('Marketplace ' . $status . ': ' . $msg);
        }
        return $data;
    }
}
