<?php

declare(strict_types=1);

namespace EasyChat\Library\Update;

use EasyChat\Library\Http\ExternalHttpClient;
use EasyChat\Library\SafeRuntime;

final class VerifiedArtifactDownloader
{
    private ExternalHttpClient $http;

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

    public function download(array $artifact, string $directory): string
    {
        $expectedSize = (int)($artifact['size_bytes'] ?? 0);
        $expectedSha = strtolower((string)($artifact['sha256'] ?? ''));
        $url = (string)($artifact['download_url'] ?? '');
        if ($expectedSize <= 0
            || $expectedSize > 100 * 1024 * 1024
            || !preg_match('/^[a-f0-9]{64}$/D', $expectedSha)
            || !$this->isTrustedDownloadUrl($url, (string)($artifact['key'] ?? ''))
            || !is_dir($directory)
            || is_link($directory)
        ) {
            throw new \RuntimeException('update_download_contract_invalid');
        }

        $path = rtrim($directory, '/\\') . '/package.zip';
        $handle = fopen($path, 'x+b');
        if (!is_resource($handle)) {
            throw new \RuntimeException('update_download_file_unavailable');
        }
        chmod($path, 0600);
        $written = 0;
        $response = [];
        try {
            $response = $this->http->request('GET', $url, [
                'timeout' => 120,
                'connect_timeout' => 8,
                'trust_profile' => 'easychat_gateway',
                'allow_tls_fallback' => true,
                'follow_location' => false,
                'headers' => ['Accept: application/octet-stream'],
                'write_function' => static function ($curl, string $chunk) use ($handle, &$written, $expectedSize): int {
                    $length = strlen($chunk);
                    if ($written + $length > $expectedSize) {
                        return 0;
                    }
                    $offset = 0;
                    while ($offset < $length) {
                        $result = fwrite($handle, substr($chunk, $offset));
                        if ($result === false || $result === 0) {
                            return 0;
                        }
                        $offset += $result;
                        $written += $result;
                    }
                    return $length;
                },
            ]);
            fflush($handle);
        } finally {
            fclose($handle);
        }

        if (empty($response['transport_ok'])
            || (int)($response['http_code'] ?? 0) !== 200
            || $written !== $expectedSize
            || (int)filesize($path) !== $expectedSize
            || !hash_equals($expectedSha, (string)hash_file('sha256', $path))
        ) {
            SafeRuntime::unlink($path);
            throw new \RuntimeException('update_download_verification_failed');
        }

        return $path;
    }

    private function isTrustedDownloadUrl(string $url, string $key): bool
    {
        if (!preg_match('/^[a-z0-9_-]{2,64}$/D', $key)) {
            return false;
        }
        $parts = parse_url($url);
        return is_array($parts)
            && strtolower((string)($parts['scheme'] ?? '')) === 'https'
            && strtolower((string)($parts['host'] ?? '')) === 'easychat24.ru'
            && (string)($parts['path'] ?? '') === '/download/' . $key
            && !isset($parts['user'], $parts['pass'], $parts['port'], $parts['query'], $parts['fragment']);
    }
}
