<?php

declare(strict_types=1);

namespace Blax\WebRtc\Media\Rust;

/**
 * Locates the prebuilt `blax-webrtc-sfu` static binary. Plug-and-play: the
 * binary is SHIPPED inside the package (rust/dist/, committed), so a plain
 * `composer require`/`update` gets a working sidecar with no download step —
 * download is only a last-resort fallback for uncommitted platforms.
 *
 * Resolution order:
 *   1. an explicit `webrtc.sfu.binary` path,
 *   2. a local dev build under the package's rust/target/{release,debug},
 *   3. the SHIPPED binary rust/dist/blax-webrtc-sfu-<platform> (committed),
 *   4. a previously installed download for this package version + platform,
 *   5. (when auto_install) download from `webrtc.sfu.download_url`, verify the
 *      sha256 from `webrtc.sfu.checksum_url`, install atomically.
 */
class BinaryManager
{
    /** The release tag whose prebuilt sidecar this package version expects. */
    public const VERSION = 'v0.1.0';

    /** @param array<string,mixed> $config the `webrtc.sfu` config block */
    public function __construct(private readonly array $config = []) {}

    /** E.g. "linux-x86_64", "linux-aarch64", "darwin-aarch64". */
    public function platform(): string
    {
        $os = match (PHP_OS_FAMILY) {
            'Darwin' => 'darwin',
            default => strtolower(PHP_OS_FAMILY),
        };
        $arch = match ($machine = php_uname('m')) {
            'arm64' => 'aarch64',
            'amd64' => 'x86_64',
            default => $machine,
        };

        return "{$os}-{$arch}";
    }

    public function version(): string
    {
        return (string) ($this->config['version'] ?? self::VERSION);
    }

    /** First usable binary (override → dev build → shipped → installed), or null. */
    public function find(): ?string
    {
        $packageRoot = dirname(__DIR__, 3);
        $devBuilds = ($this->config['dev_builds'] ?? true) === true;
        $candidates = array_filter([
            $this->config['binary'] ?? null,
            $devBuilds ? $packageRoot.'/rust/target/release/blax-webrtc-sfu' : null,
            $devBuilds ? $packageRoot.'/rust/target/debug/blax-webrtc-sfu' : null,
            // Plug-and-play: the prebuilt binary SHIPPED with the package (composer
            // installs it alongside the PHP), resolved per platform. This is what
            // makes backend-hosted rooms work on `composer require`/`update` with
            // no download step — see rust/dist/ (committed).
            $packageRoot.'/rust/dist/blax-webrtc-sfu-'.$this->platform(),
            $this->installPath(),
        ]);

        foreach ($candidates as $path) {
            if (is_file($path) && is_executable($path)) {
                return $path;
            }
        }

        return null;
    }

    /** Where a downloaded binary lives (versioned + per platform). */
    public function installPath(): string
    {
        $dir = rtrim((string) ($this->config['install_path'] ?? sys_get_temp_dir().'/blax-webrtc'), '/');

        return "{$dir}/blax-webrtc-sfu-{$this->version()}-{$this->platform()}";
    }

    /** Find or (if allowed) download — the plug-and-play entrypoint. */
    public function ensureInstalled(): string
    {
        if ($found = $this->find()) {
            return $found;
        }

        if (($this->config['auto_install'] ?? true) !== true) {
            throw new SidecarException(
                'SFU sidecar binary not found and webrtc.sfu.auto_install is disabled — '.
                'run `php artisan webrtc:install`, build rust/ locally, or set webrtc.sfu.binary.'
            );
        }

        return $this->install();
    }

    public function install(bool $force = false): string
    {
        $target = $this->installPath();
        if (! $force && is_file($target)) {
            return $target;
        }

        $url = $this->url('download_url');
        if ($url === '') {
            throw new SidecarException('webrtc.sfu.download_url is not configured');
        }

        $binary = $this->fetch($url);

        // A configured checksum_url is a hard requirement, not best-effort —
        // set it to null explicitly to opt out of verification.
        if (($checksumUrl = $this->url('checksum_url')) !== '') {
            $expected = strtolower((string) strtok(trim($this->fetch($checksumUrl)), " \t"));
            $actual = hash('sha256', $binary);
            if (! hash_equals($expected, $actual)) {
                throw new SidecarException(
                    "sidecar checksum mismatch for {$url}: expected {$expected}, got {$actual}"
                );
            }
        }

        if (! is_dir($dir = dirname($target))) {
            @mkdir($dir, 0775, true);
        }

        $tmp = $target.'.download.'.getmypid();
        if (@file_put_contents($tmp, $binary) === false) {
            throw new SidecarException("cannot write sidecar binary to {$tmp}");
        }
        @chmod($tmp, 0755);
        if (! @rename($tmp, $target)) {
            @unlink($tmp);
            throw new SidecarException("cannot move sidecar binary into place at {$target}");
        }

        return $target;
    }

    private function url(string $key): string
    {
        return str_replace(
            ['{version}', '{platform}'],
            [$this->version(), $this->platform()],
            (string) ($this->config[$key] ?? ''),
        );
    }

    private function fetch(string $url): string
    {
        $context = stream_context_create([
            'http' => ['timeout' => (float) ($this->config['download_timeout'] ?? 60), 'follow_location' => 1],
        ]);

        $data = @file_get_contents($url, false, $context);
        if ($data === false || $data === '') {
            throw new SidecarException("sidecar download failed: {$url}");
        }

        return $data;
    }
}
