<?php
/* IJ-SLIP/2 R77H dual-generation final-RN Mirror selector. */

function r77h_b64u_encode($value) {
    return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
}

function r77h_load_signed_manifest($canonicalPath, $signaturePath, $authorityPath, $expectedGeneration, $expectedHash) {
    $canonical = @file_get_contents($canonicalPath);
    $signature = @file_get_contents($signaturePath);
    $authority = @file_get_contents($authorityPath);
    if ($canonical === false || $signature === false || $authority === false) {
        throw new RuntimeException('R77H signed inventory is incomplete');
    }
    $publicKey = openssl_pkey_get_public($authority);
    if ($publicKey === false || openssl_verify($canonical, $signature, $publicKey, OPENSSL_ALGO_SHA256) !== 1) {
        throw new RuntimeException('R77H signed inventory verification failed');
    }
    $manifest = json_decode($canonical, true);
    if (!is_array($manifest) || !isset($manifest['protocol'], $manifest['generation'], $manifest['expires_at'], $manifest['nodes'], $manifest['mirrors'])) {
        throw new RuntimeException('R77H signed inventory is malformed');
    }
    $actualHash = hash('sha256', $canonical);
    if ($manifest['protocol'] !== 'IJ-SLIP/2' || intval($manifest['generation']) !== intval($expectedGeneration) ||
        !in_array(intval($expectedGeneration), array(3, 4), true) || !hash_equals(strtolower($expectedHash), $actualHash) ||
        intval($manifest['expires_at']) <= time()) {
        throw new RuntimeException('R77H signed inventory is invalid, mismatched, or expired');
    }
    return array(
        'canonical' => $canonical,
        'hash' => $actualHash,
        'document' => $manifest
    );
}

function r77h_manifest_node($manifest, $nodeId) {
    foreach ($manifest['document']['nodes'] as $node) {
        if (!empty($node['active']) && isset($node['id']) && $node['id'] === $nodeId &&
            isset($node['roles']) && in_array('rn', $node['roles'], true)) {
            return $node;
        }
    }
    throw new RuntimeException('R77H terminal RN is absent from signed inventory');
}

function r77h_pick_weighted_mirror($manifest) {
    $eligible = array();
    $total = 0;
    foreach ($manifest['document']['mirrors'] as $mirror) {
        if (empty($mirror['active']) || empty($mirror['id']) || empty($mirror['url']) || strpos($mirror['url'], 'https://') !== 0) continue;
        $weight = isset($mirror['weight']) ? max(1, intval($mirror['weight'])) : 1;
        $mirror['_r77h_weight'] = $weight;
        $eligible[] = $mirror;
        $total += $weight;
    }
    if (!$eligible || $total < 1) throw new RuntimeException('R77H has no eligible Mirror');
    $draw = random_int(1, $total);
    foreach ($eligible as $mirror) {
        $draw -= $mirror['_r77h_weight'];
        if ($draw <= 0) {
            unset($mirror['_r77h_weight']);
            return $mirror;
        }
    }
    throw new RuntimeException('R77H Mirror selection failed');
}

function ss2_final_rn_pick_mirror_r77h($context, $nodeId, $privateKeyPath, $canonicalPath, $signaturePath, $authorityPath, $expectedGeneration, $expectedHash) {
    foreach (array('trip_id', 'spike_binding', 'route_request_nonce') as $required) {
        if (!isset($context[$required]) || !is_string($context[$required]) || $context[$required] === '') {
            throw new InvalidArgumentException('R77H selection context is incomplete');
        }
    }
    $manifest = r77h_load_signed_manifest($canonicalPath, $signaturePath, $authorityPath, $expectedGeneration, $expectedHash);
    $node = r77h_manifest_node($manifest, $nodeId);
    $mirror = r77h_pick_weighted_mirror($manifest);
    $now = time();
    $proof = array(
        'protocol' => 'IJ-SLIP/2',
        'proof_type' => 'final-rn-mirror-selection',
        'trip_id' => $context['trip_id'],
        'spike_binding' => $context['spike_binding'],
        'route_request_nonce' => $context['route_request_nonce'],
        'terminal_node_id' => $nodeId,
        'terminal_node_key_id' => $node['capsule_public_key_id'],
        'mirror_id' => $mirror['id'],
        'mirror_url' => $mirror['url'],
        'manifest_generation' => intval($manifest['document']['generation']),
        'manifest_sha256' => $manifest['hash'],
        'issued_at' => $now,
        'expires_at' => $now + 30,
        'selection_nonce' => r77h_b64u_encode(random_bytes(18))
    );
    $payload = json_encode($proof, JSON_UNESCAPED_SLASHES);
    if (!is_string($payload)) throw new RuntimeException('R77H proof encoding failed');
    $privatePem = @file_get_contents($privateKeyPath);
    if ($privatePem === false) throw new RuntimeException('R77H terminal RN key is unavailable');
    $signature = '';
    if (!openssl_sign($payload, $signature, $privatePem, OPENSSL_ALGO_SHA256)) {
        throw new RuntimeException('R77H terminal proof signing failed');
    }
    return array(
        'mirror' => $mirror,
        'selection_payload_b64url' => r77h_b64u_encode($payload),
        'selection_signature_b64url' => r77h_b64u_encode($signature),
        'selection_key_id' => $node['capsule_public_key_id']
    );
}
