<?php

namespace CaptureRelay;

use YahnisElsts\PluginUpdateChecker\v5\PucFactory;

/**
 * Self-hosted plugin updates.
 *
 * PUC owns the normal WordPress update UI. This class adds one integrity gate
 * for CaptureRelay packages before WordPress unpacks or installs them.
 */
final class Updater
{
    public const METADATA_URL = 'https://www.capturerelay.app/plugin/update.json';
    private const MANIFEST_TRANSIENT = 'capture_relay_update_manifest';
    private const MANIFEST_TTL = 300;

    private static bool $registered = false;

    public static function register(): void
    {
        if (self::$registered) {
            return;
        }
        self::$registered = true;

        $library = CAPTURE_RELAY_DIR . 'vendor/plugin-update-checker/plugin-update-checker.php';
        if (!is_readable($library)) {
            return;
        }

        try {
            require_once $library;
            if (!class_exists(PucFactory::class)) {
                return;
            }

            PucFactory::buildUpdateChecker(
                self::METADATA_URL,
                CAPTURE_RELAY_FILE,
                'capture-relay'
            );
            add_filter('upgrader_pre_download', [self::class, 'verifyPackage'], 10, 4);
        } catch (\Throwable $exception) {
            // A failed optional update integration must never take down WP.
            return;
        }
    }

    /**
     * Download and verify only a CaptureRelay package. Returning a local file
     * path lets WP continue through its normal upgrader and auto-update flow.
     *
     * @param mixed $reply Existing pre-download result.
     * @param string $package Package URL supplied by WordPress.
     * @param mixed $upgrader Current upgrader instance.
     * @param array $hookExtra Upgrader context.
     * @return mixed
     */
    public static function verifyPackage($reply, string $package, $upgrader, array $hookExtra = [])
    {
        // Preserve a veto returned by another upgrader integration.
        if ($reply instanceof \WP_Error) {
            return $reply;
        }
        if (!self::isCaptureRelayUpdate($package, $hookExtra)) {
            return $reply;
        }

        $manifest = self::manifest();
        if ($manifest === null) {
            return self::error(
                'capture_relay_update_manifest_unavailable',
                __('CaptureRelay could not verify the update manifest. The update was not installed.', 'capture-relay')
            );
        }

        $expectedUrl = (string) ($manifest['download_url'] ?? '');
        $expectedHash = strtolower((string) ($manifest['sha256'] ?? ''));
        if (!self::isHttpsUrl($expectedUrl) || !preg_match('/^[a-f0-9]{64}$/', $expectedHash)) {
            return self::error(
                'capture_relay_update_manifest_invalid',
                __('CaptureRelay received an invalid update manifest. The update was not installed.', 'capture-relay')
            );
        }
        if (!hash_equals($expectedUrl, $package)) {
            return self::error(
                'capture_relay_update_package_mismatch',
                __('CaptureRelay rejected an unexpected update package URL.', 'capture-relay')
            );
        }

        if (!function_exists('download_url')) {
            return self::error(
                'capture_relay_update_download_unavailable',
                __('CaptureRelay could not access the WordPress update downloader.', 'capture-relay')
            );
        }

        $download = download_url($package, 60);
        if (is_wp_error($download)) {
            return $download;
        }

        $actualHash = @hash_file('sha256', $download);
        if (!is_string($actualHash) || !hash_equals($expectedHash, strtolower($actualHash))) {
            if (is_string($download) && is_file($download)) {
                @unlink($download);
            }
            return self::error(
                'capture_relay_update_checksum_mismatch',
                __('CaptureRelay rejected the update because its SHA-256 checksum did not match the signed manifest.', 'capture-relay')
            );
        }

        return $download;
    }

    /** @return array<string,mixed>|null */
    private static function manifest(): ?array
    {
        $cached = get_transient(self::MANIFEST_TRANSIENT);
        if (is_array($cached)) {
            return $cached;
        }
        if (!function_exists('wp_safe_remote_get')) {
            return null;
        }

        $response = wp_safe_remote_get(self::METADATA_URL, [
            'timeout' => 5,
            'headers' => ['Accept' => 'application/json'],
        ]);
        if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
            return null;
        }

        $decoded = json_decode((string) wp_remote_retrieve_body($response), true);
        if (!is_array($decoded)) {
            return null;
        }
        set_transient(self::MANIFEST_TRANSIENT, $decoded, self::MANIFEST_TTL);
        return $decoded;
    }

    private static function isCaptureRelayUpdate(string $package, array $hookExtra): bool
    {
        $basename = plugin_basename(CAPTURE_RELAY_FILE);
        $plugin = $hookExtra['plugin'] ?? null;
        if (is_string($plugin) && $plugin === $basename) {
            return true;
        }
        $plugins = $hookExtra['plugins'] ?? [];
        return is_array($plugins) && in_array($basename, $plugins, true);
    }

    private static function isHttpsUrl(string $url): bool
    {
        return (bool) preg_match('#^https://[^/]+(?:/.*)?$#i', $url);
    }

    private static function error(string $code, string $message): \WP_Error
    {
        return new \WP_Error($code, $message);
    }
}
