<?php
declare(strict_types=1);

/** Alpine BE WireGuard peer lifecycle (wg set) for remote-access sessions. */
final class WireGuardPeerProvisioner {
    private function __construct() {}

    public static function isEnabled(): bool {
        return (bool) cfg('remote_access.provision_peers', true);
    }

    public static function interfaceName(): string {
        return (string) cfg('remote_access.wg_interface', 'wg0');
    }

    public static function publicKeyFromPrivate(string $privateKey): string {
        $priv = trim($privateKey);
        if ($priv === '') {
            return '';
        }
        $pub = trim((string) @shell_exec('echo ' . escapeshellarg($priv) . ' | wg pubkey 2>/dev/null') ?: '');
        return $pub;
    }

    public static function addClientPeer(string $clientPublicKey, string $clientAddressCidr): bool {
        if (!self::isEnabled()) {
            return true;
        }
        $pub = trim($clientPublicKey);
        if ($pub === '' || str_starts_with($pub, 'dev-placeholder-pub-')) {
            return false;
        }
        $iface = self::interfaceName();
        $allowed = trim($clientAddressCidr);
        if ($allowed === '') {
            $allowed = WireGuardAddressPool::allocateClientAddress('default');
        }
        $code = self::runWg(
            'set ' . escapeshellarg($iface)
            . ' peer ' . escapeshellarg($pub)
            . ' allowed-ips ' . escapeshellarg($allowed)
        );
        return $code === 0;
    }

    public static function removeClientPeer(?string $clientPublicKey): void {
        if (!self::isEnabled()) {
            return;
        }
        $pub = trim((string) $clientPublicKey);
        if ($pub === '' || str_starts_with($pub, 'dev-placeholder-pub-')) {
            return;
        }
        $iface = self::interfaceName();
        self::runWg(
            'set ' . escapeshellarg($iface)
            . ' peer ' . escapeshellarg($pub)
            . ' remove'
        );
    }

    /** @return list<array{public_key: string, allowed_ips: string}> */
    public static function listPeerDumpRows(): array {
        if (!self::isEnabled()) {
            return [];
        }
        $iface = self::interfaceName();
        $prefix = rtrim((string) cfg('remote_access.wg_set_prefix', ''), " \t");
        $cmd = ($prefix !== '' ? $prefix . ' ' : '') . 'wg show ' . escapeshellarg($iface) . ' dump 2>/dev/null';
        $raw = @shell_exec($cmd);
        if (!is_string($raw) || trim($raw) === '') {
            return [];
        }
        $rows = [];
        foreach (preg_split('/\r\n|\n|\r/', trim($raw)) as $line) {
            if ($line === '' || str_starts_with($line, 'interface:')) {
                continue;
            }
            $cols = explode("\t", $line);
            if (count($cols) < 4) {
                continue;
            }
            $pub = trim($cols[0]);
            if ($pub === '') {
                continue;
            }
            $rows[] = [
                'public_key' => $pub,
                'allowed_ips' => trim((string) ($cols[3] ?? '')),
            ];
        }
        return $rows;
    }

    /**
     * Remove live wg peers not tied to pending/active sessions (or with empty AllowedIPs).
     *
     * @param list<string> $keepPublicKeys
     * @return list<array{public_key: string, allowed_ips: string, reason: string}>
     */
    public static function pruneOrphanPeers(array $keepPublicKeys, bool $dryRun = false): array {
        if (!self::isEnabled()) {
            return [];
        }
        $keep = [];
        foreach ($keepPublicKeys as $key) {
            $pub = trim((string) $key);
            if ($pub !== '') {
                $keep[$pub] = true;
            }
        }
        $pruned = [];
        foreach (self::listPeerDumpRows() as $row) {
            $pub = $row['public_key'];
            $allowed = trim($row['allowed_ips']);
            $broken = ($allowed === '' || $allowed === '(none)');
            $orphan = !isset($keep[$pub]);
            if (!$orphan && !$broken) {
                continue;
            }
            if (!$dryRun) {
                self::removeClientPeer($pub);
            }
            $pruned[] = [
                'public_key' => $pub,
                'allowed_ips' => $allowed,
                'reason' => $broken ? 'broken_allowed_ips' : 'orphan',
            ];
        }
        return $pruned;
    }

    private static function runWg(string $args): int {
        $prefix = rtrim((string) cfg('remote_access.wg_set_prefix', ''), " \t");
        $cmd = ($prefix !== '' ? $prefix . ' ' : '') . 'wg ' . $args;
        exec($cmd . ' 2>&1', $out, $code);
        if ($code !== 0) {
            error_log('WireGuardPeerProvisioner: ' . $cmd . ' failed: ' . implode(' ', $out));
        }
        return $code;
    }
}
