// Asserts that the WASM-compiled `ui/core` produces canonical bytes
// byte-for-byte identical to the gateway-side fixtures committed in
// `ui/core/canon/testdata/`. The fixtures themselves are exercised by
// the Go-side parity test in `gateway/authn/parity_with_ui_core_test.go`,
// so passing both proves Go-Go parity AND Go-TS parity through WASM.
//
// The signature parity check additionally proves that a signature
// produced by Node's `ed25519` over the WASM-built canonical bytes is
// accepted by the gateway's `VerifyResponseSignature` /
// `VerifyEventSignature` (re-checked here via Core.verifyResponse /
// Core.verifyEvent against the same fixture key).

import {
	createPrivateKey,
	createPublicKey,
	sign as cryptoSign,
} from "node:crypto";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { beforeAll, describe, expect, test } from "vitest";
import type { Core } from "../src/platform/core/index";
import { loadWasmCoreForTest } from "./setup-wasm";

const TESTDATA_DIR = resolve(
	process.cwd(),
	"..",
	"core",
	"canon",
	"testdata",
) + "/";

interface RequestFixture {
	protocol_version: string;
	device_session_id: string;
	message_type: string;
	timestamp_ms: number;
	request_id: string;
	payload: string;
	payload_hash_hex: string;
	expected_canonical_bytes_hex: string;
	private_key_seed_hex: string;
	public_key_base64: string;
	expected_signature_hex: string;
}

interface ResponseFixture {
	protocol_version: string;
	request_id: string;
	timestamp_ms: number;
	result_code: string;
	payload: string;
	payload_hash_hex: string;
	expected_canonical_bytes_hex: string;
	private_key_seed_hex: string;
	public_key_base64: string;
	expected_signature_hex: string;
}

interface EventFixture {
	event_type: string;
	event_id: string;
	timestamp_ms: number;
	request_id: string;
	trace_id: string;
	payload: string;
	payload_hash_hex: string;
	expected_canonical_bytes_hex: string;
	private_key_seed_hex: string;
	public_key_base64: string;
	expected_signature_hex: string;
}

let core: Core;

beforeAll(async () => {
	core = await loadWasmCoreForTest();
});

describe("WasmCore canon parity with gateway fixtures", () => {
	test.each([
		"request_user_account_get.json",
		"request_user_games_command.json",
		"request_lobby_my_games_list.json",
	])("%s — canonical bytes byte-for-byte equal", (name) => {
		const fixture = readJson<RequestFixture>(name);
		const canonical = core.signRequest({
			protocolVersion: fixture.protocol_version,
			deviceSessionId: fixture.device_session_id,
			messageType: fixture.message_type,
			timestampMs: BigInt(fixture.timestamp_ms),
			requestId: fixture.request_id,
			payloadHash: hexToBytes(fixture.payload_hash_hex),
		});
		expect(bytesToHex(canonical)).toBe(fixture.expected_canonical_bytes_hex);

		const signature = signEd25519(
			hexToBytes(fixture.private_key_seed_hex),
			canonical,
		);
		expect(bytesToHex(signature)).toBe(fixture.expected_signature_hex);
	});

	test("response_ok.json — verifyResponse accepts canonical signature", () => {
		const fixture = readJson<ResponseFixture>("response_ok.json");
		const publicKey = base64ToBytes(fixture.public_key_base64);
		const signature = hexToBytes(fixture.expected_signature_hex);
		const fields = {
			protocolVersion: fixture.protocol_version,
			requestId: fixture.request_id,
			timestampMs: BigInt(fixture.timestamp_ms),
			resultCode: fixture.result_code,
			payloadHash: hexToBytes(fixture.payload_hash_hex),
		};
		expect(core.verifyResponse(publicKey, signature, fields)).toBe(true);

		const tampered = new Uint8Array(signature);
		tampered[0] ^= 0xff;
		expect(core.verifyResponse(publicKey, tampered, fields)).toBe(false);
	});

	test("event_gateway_server_time.json — verifyEvent accepts canonical signature", () => {
		const fixture = readJson<EventFixture>("event_gateway_server_time.json");
		const publicKey = base64ToBytes(fixture.public_key_base64);
		const signature = hexToBytes(fixture.expected_signature_hex);
		const fields = {
			eventType: fixture.event_type,
			eventId: fixture.event_id,
			timestampMs: BigInt(fixture.timestamp_ms),
			requestId: fixture.request_id,
			traceId: fixture.trace_id,
			payloadHash: hexToBytes(fixture.payload_hash_hex),
		};
		expect(core.verifyEvent(publicKey, signature, fields)).toBe(true);

		const tampered = new Uint8Array(signature);
		tampered[0] ^= 0xff;
		expect(core.verifyEvent(publicKey, tampered, fields)).toBe(false);
	});
});

function readJson<T>(name: string): T {
	return JSON.parse(readFileSync(`${TESTDATA_DIR}${name}`, "utf8")) as T;
}

function hexToBytes(value: string): Uint8Array {
	const length = value.length / 2;
	const out = new Uint8Array(length);
	for (let i = 0; i < length; i++) {
		out[i] = parseInt(value.substr(i * 2, 2), 16);
	}
	return out;
}

function bytesToHex(value: Uint8Array): string {
	return Array.from(value, (byte) =>
		byte.toString(16).padStart(2, "0"),
	).join("");
}

function base64ToBytes(value: string): Uint8Array {
	return Uint8Array.from(Buffer.from(value, "base64"));
}

// Node's `ed25519` accepts a raw 32-byte seed packaged as a PKCS#8 DER
// blob; this helper synthesises that blob so the test fixture seed
// matches the Go-side `ed25519.NewKeyFromSeed` shape used by the
// committed canon golden values.
function signEd25519(seed: Uint8Array, message: Uint8Array): Uint8Array {
	const pkcs8 = pkcs8FromEd25519Seed(seed);
	const key = createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" });
	const signature = cryptoSign(null, message, key);
	return new Uint8Array(signature);
}

function pkcs8FromEd25519Seed(seed: Uint8Array): Buffer {
	if (seed.length !== 32) {
		throw new Error(`ed25519 seed must be 32 bytes, got ${seed.length}`);
	}
	// PKCS#8 wrapping for an Ed25519 raw seed:
	//   SEQUENCE {
	//     INTEGER 0,
	//     SEQUENCE { OID 1.3.101.112 (Ed25519) },
	//     OCTET STRING { OCTET STRING { seed } }
	//   }
	const prefix = Buffer.from([
		0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70,
		0x04, 0x22, 0x04, 0x20,
	]);
	return Buffer.concat([prefix, Buffer.from(seed)]);
}

// `createPublicKey` is imported only so future tests can derive the
// public key from the same seed without going through the WASM bridge.
// Suppress the unused-import warning by referencing the binding here.
void createPublicKey;
