<?php

namespace App\Services\Marketplace;

use App\Core\Database;
use App\Models\Setting;
use App\Services\Marketplace\Exceptions\RegistryPolicyException;

/**
 * Enforces which registry URLs the CMS client may talk to.
 *
 * Rules:
 *  1. HTTPS-only (unless dev_allow_http_registry is set).
 *  2. The official registry is always allowed.
 *  3. Custom registries must be listed in iw_marketplace_allowlist.
 */
class RegistryPolicy
{
    // Official registry – hardcoded, cannot be overridden via settings.
    public const OFFICIAL_REGISTRY      = 'https://itsweber.de/api/v1/registry';
    public const OFFICIAL_KEY_ID        = 'itsweber-primary';

    // Ed25519 public key for the official registry.
    // Filled in during Session 2 (server bootstrap).
    // Base64-encoded 32-byte public key.
    public const OFFICIAL_PUBLIC_KEY_B64 = '5Y+po2WLNY1o/X8b3gOrTVEHpulTdUZf4V1xcpgad5M=';

    private Database $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    // ─────────────────────────────────────────────────────────
    // Public API
    // ─────────────────────────────────────────────────────────

    /**
     * Assert that $url is a policy-allowed registry.
     * Throws RegistryPolicyException on violation.
     */
    public function assertAllowed(string $url): void
    {
        $this->assertHttps($url);

        if (!$this->isAllowed($url)) {
            throw new RegistryPolicyException(
                "Registry URL is not in the allowlist: {$url}"
            );
        }
    }

    /**
     * Return true if $url is the official registry.
     */
    public function isOfficial(string $url): bool
    {
        return rtrim($url, '/') === rtrim(self::OFFICIAL_REGISTRY, '/');
    }

    /**
     * Return true if $url is allowed (official or in allowlist).
     */
    public function isAllowed(string $url): bool
    {
        if ($this->isOfficial($url)) {
            return true;
        }

        return $this->db->fetch(
            "SELECT id FROM iw_marketplace_allowlist WHERE registry_url = ? LIMIT 1",
            [rtrim($url, '/')]
        ) !== null;
    }

    /**
     * Return trusted public keys for the given registry URL.
     *
     * For the official registry the hardcoded key is returned.
     * For custom registries the key stored in iw_marketplace_allowlist is returned.
     *
     * @return array<array{key_id: string, public_key_b64: string}>
     * @throws RegistryPolicyException when no key is found for a non-official registry.
     */
    public function getTrustedPublicKeys(string $url): array
    {
        if ($this->isOfficial($url)) {
            if (self::OFFICIAL_PUBLIC_KEY_B64 === '') {
                // Key not yet provisioned – fail safe.
                throw new RegistryPolicyException(
                    'Official registry public key is not configured. ' .
                    'Set OFFICIAL_PUBLIC_KEY_B64 in RegistryPolicy after server bootstrap.'
                );
            }
            return [[
                'key_id'         => self::OFFICIAL_KEY_ID,
                'public_key_b64' => self::OFFICIAL_PUBLIC_KEY_B64,
            ]];
        }

        $row = $this->db->fetch(
            "SELECT key_id, public_key_b64 FROM iw_marketplace_allowlist WHERE registry_url = ? LIMIT 1",
            [rtrim($url, '/')]
        );

        if (!$row) {
            throw new RegistryPolicyException(
                "No public key found for registry: {$url}"
            );
        }

        return [[
            'key_id'         => $row['key_id'],
            'public_key_b64' => $row['public_key_b64'],
        ]];
    }

    // ─────────────────────────────────────────────────────────
    // Private helpers
    // ─────────────────────────────────────────────────────────

    /**
     * Reject plain HTTP unless dev_allow_http_registry is explicitly set.
     * HTTPS enforcement is a hard security boundary; only bypass for local dev.
     */
    private function assertHttps(string $url): void
    {
        if (str_starts_with(strtolower($url), 'http://')) {
            $devBypass = Setting::get('dev_allow_http_registry', '0');
            if ($devBypass !== '1') {
                throw new RegistryPolicyException(
                    'Registry URLs must use HTTPS. Plain HTTP is not allowed.'
                );
            }
        }
    }
}
