<?php
declare(strict_types=1);

// Offline validator: reads private synthetic HTTP evidence; no DB or HTTP calls.
$files = [
    'B' => (string)getenv('CDX_FIXEDVECTOR_B_RAW'),
    'C' => (string)getenv('CDX_FIXEDVECTOR_C_RAW'),
];
foreach ($files as $path) {
    if ($path === '' || !is_file($path)) {
        echo json_encode(['status' => 'missing_private_raw_evidence']), PHP_EOL;
        exit(78);
    }
}
$expectedFileSha = [
    'B' => '39bb288be2b5b7fa2ce531830f54140a52376b6f5ab3fae59e82216a66457a6a',
    'C' => 'c795d8f44f943766c559740e953bfc9b2247f2c5c0ee42b7137a480220083c06',
];
$canonical = null;
$canonical = static function ($value) use (&$canonical) {
    if (!is_array($value)) return $value;
    foreach ($value as $key => $item) $value[$key] = $canonical($item);
    if ($value !== [] && array_keys($value) !== range(0, count($value) - 1)) {
        ksort($value, SORT_STRING);
    }
    return $value;
};
$caseHashes = [];
$lastSync = [];
$fileHashes = [];
foreach ($files as $side => $path) {
    $fileHashes[$side] = hash_file('sha256', $path);
    if (!hash_equals($expectedFileSha[$side], $fileHashes[$side])) {
        echo json_encode(['status' => 'private_raw_sha_mismatch', 'side' => $side]), PHP_EOL;
        exit(78);
    }
    $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if (!is_array($lines) || count($lines) !== 4) {
        echo json_encode(['status' => 'unexpected_case_count', 'side' => $side]), PHP_EOL;
        exit(78);
    }
    foreach ($lines as $index => $line) {
        $record = json_decode($line, true);
        $body = json_decode((string)($record['response_raw'] ?? ''), true);
        if (!is_array($record) || !is_array($body)
            || (int)($record['http'] ?? 0) !== 200
            || (int)($body['status'] ?? 0) !== 200) {
            echo json_encode(['status' => 'invalid_case_body', 'side' => $side,
                'case_index' => $index]), PHP_EOL;
            exit(78);
        }
        if ($index < 2) {
            $value = $body['data']['last_sync_at'] ?? null;
            $lastSync[$side][$index] = [
                'type' => gettype($value),
                'empty_string' => is_string($value) && $value === '',
                'seconds_datetime' => is_string($value)
                    && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value) === 1,
                'microseconds_datetime' => is_string($value)
                    && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+$/', $value) === 1,
            ];
        }
        // trace_id is generated afresh by the error/response wrapper. Nothing
        // else, including data.last_sync_at, is removed or coerced.
        unset($body['trace_id']);
        $caseHashes[$side][$index] = hash('sha256', json_encode(
            $canonical($body), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    }
}
$equal = $caseHashes['B'] === $caseHashes['C'];
$lastSyncEqual = $lastSync['B'] === $lastSync['C'];
echo json_encode(['status' => $equal && $lastSyncEqual
        ? 'all_full_business_bodies_equal' : 'response_difference',
    'source_file_sha256' => $fileHashes,
    'case_hashes_after_trace_id_only' => $caseHashes,
    'last_sync_at_type_and_format' => $lastSync,
    'full_body_equal' => $equal,
    'last_sync_at_equal' => $lastSyncEqual,
    'database_or_http_calls' => 0], JSON_UNESCAPED_SLASHES), PHP_EOL;
exit($equal && $lastSyncEqual ? 0 : 4);
