//! Transport-compatibility fixtures generated by the reference TypeScript.

use keetanetwork_block::{Block, BlockPurpose, BlockVersion, Hashable, UnsignedBlock};
use serde::Deserialize;

#[derive(Deserialize)]
struct Fixture {
	name: String,
	bytes: String,
	unsigned_bytes: String,
	hash: String,
	version: u8,
	purpose: u8,
	network: String,
	subnet: Option<String>,
	idempotent: Option<String>,
	date_ms: i64,
	account: String,
	previous: String,
	operation_count: usize,
	signature_count: usize,
	signatures: Vec<String>,
}

fn fixtures() -> Vec<Fixture> {
	let raw = include_str!("fixtures/blocks.json");
	serde_json::from_str(raw).expect("fixtures must parse")
}

fn decode_signed(fixture: &Fixture) -> Block {
	let bytes = hex::decode(&fixture.bytes).expect("fixture bytes must be hex");
	Block::try_from(bytes.as_slice()).unwrap_or_else(|error| panic!("fixture {} must decode: {error}", fixture.name))
}

fn decode_unsigned(fixture: &Fixture) -> UnsignedBlock {
	let bytes = hex::decode(&fixture.unsigned_bytes).expect("fixture bytes must be hex");
	UnsignedBlock::try_from(bytes.as_slice())
		.unwrap_or_else(|error| panic!("fixture {} must decode unsigned: {error}", fixture.name))
}

#[test]
fn test_fixture_blocks_decode_and_reencode() {
	let fixtures = fixtures();
	assert!(!fixtures.is_empty(), "fixture set must not be empty");

	for fixture in &fixtures {
		let block = decode_signed(fixture);

		let re_encoded = hex::encode_upper(block.to_bytes());
		let hash = block.hash().to_string();
		assert_eq!(re_encoded, fixture.bytes, "fixture {} re-encoded bytes must match", fixture.name);
		assert_eq!(hash, fixture.hash, "fixture {} hash must match", fixture.name);
	}
}

#[test]
fn test_fixture_unsigned_blocks_decode_and_reencode() {
	for fixture in &fixtures() {
		let unsigned = decode_unsigned(fixture);

		let re_encoded = hex::encode_upper(unsigned.to_bytes());
		let hash = unsigned.hash().to_string();
		assert_eq!(re_encoded, fixture.unsigned_bytes, "fixture {} unsigned bytes must match", fixture.name);
		assert_eq!(hash, fixture.hash, "fixture {} unsigned hash must match", fixture.name);
	}
}

#[test]
fn test_fixture_fields_match() {
	for fixture in &fixtures() {
		let block = decode_signed(fixture);
		let data = block.data();

		let version = match data.version() {
			BlockVersion::V1 => 1,
			BlockVersion::V2 => 2,
		};
		assert_eq!(version, fixture.version, "fixture {} version", fixture.name);

		let purpose = match data.purpose() {
			BlockPurpose::Generic => 0,
			BlockPurpose::Fee => 1,
		};
		assert_eq!(purpose, fixture.purpose, "fixture {} purpose", fixture.name);

		let network = data.network().to_string();
		assert_eq!(network, fixture.network, "fixture {} network", fixture.name);

		let subnet = data.subnet().map(|subnet| subnet.to_string());
		assert_eq!(subnet, fixture.subnet, "fixture {} subnet", fixture.name);

		let idempotent = data.idempotent().map(hex::encode_upper);
		assert_eq!(idempotent, fixture.idempotent, "fixture {} idempotent", fixture.name);

		let date_ms = data.date().unix_millis();
		assert_eq!(date_ms, fixture.date_ms, "fixture {} date", fixture.name);

		let account = data.account().to_string();
		assert_eq!(account, fixture.account, "fixture {} account", fixture.name);

		let previous = data.previous().to_string();
		assert_eq!(previous, fixture.previous, "fixture {} previous", fixture.name);

		let operation_count = data.operations().len();
		assert_eq!(operation_count, fixture.operation_count, "fixture {} operation count", fixture.name);

		let signature_count = block.signatures().len();
		assert_eq!(signature_count, fixture.signature_count, "fixture {} signature count", fixture.name);

		let signatures: Vec<String> = block
			.signatures()
			.iter()
			.map(|signature| hex::encode_upper(signature.as_ref()))
			.collect();
		assert_eq!(signatures, fixture.signatures, "fixture {} signatures", fixture.name);
	}
}

#[test]
fn test_fixture_corrupted_signature_rejected() {
	for fixture in &fixtures() {
		let mut bytes = hex::decode(&fixture.bytes).expect("fixture bytes must be hex");
		let last = bytes.len() - 1;

		bytes[last] ^= 0xFF;
		assert!(
			Block::try_from(bytes.as_slice()).is_err(),
			"fixture {} with corrupted signature must be rejected",
			fixture.name
		);
	}
}
