//! v0.3 W2 KAT: cross-validate the PEM / PKCS#8 / SPKI / SEC1 codecs
//! against committed gmssl 3.1.1 fixtures.
//!
//! Fixtures live in `tests/data/`. See `tests/data/README.md` for the
//! regen recipe and the structural breakdown of each file.
//!
//! These tests run **always** — they don't need the
//! `GMCRYPTO_GMSSL=1` env gate, because the fixtures are committed
//! binary blobs, not live-generated by gmssl on each run. CI runners
//! without gmssl still execute them.

use gmcrypto_core::{pem, pkcs8, spki};
use subtle::ConstantTimeEq;

const ENCRYPTED_PKCS8_PEM: &str = include_str!("data/v0_3-sm2-pkcs8-encrypted.pem");
const SPKI_PEM: &str = include_str!("data/v0_3-sm2-spki.pem");
const FIXTURE_PASSWORD: &[u8] = b"passw0rd";

/// PEM-decode the SPKI fixture and assert the underlying point is
/// a valid SM2 public key.
#[test]
fn gmssl_spki_decodes() {
    let der = pem::decode(SPKI_PEM, "PUBLIC KEY").expect("PEM decode SPKI");
    let pubkey = spki::decode(&der).expect("SPKI decode succeeds for gmssl-emitted blob");
    // Re-encode and round-trip; gmssl emits canonical DER, so re-encoding
    // should match byte-for-byte.
    let re_encoded = spki::encode(&pubkey);
    assert_eq!(re_encoded, der, "SPKI re-encode must be byte-identical");
}

/// PEM-decode the encrypted PKCS#8 fixture, decrypt with the known
/// password, and confirm the recovered private key is well-formed.
#[test]
fn gmssl_encrypted_pkcs8_decrypts() {
    let der = pem::decode(ENCRYPTED_PKCS8_PEM, "ENCRYPTED PRIVATE KEY").expect("PEM decode PKCS#8");
    let _privkey = pkcs8::decrypt(&der, FIXTURE_PASSWORD).expect("PKCS#8 decrypt with password");
    // Recovered. Successful reconstruction proves:
    // - PBES2 / PBKDF2-HMAC-SM3 / SM4-CBC chain works end-to-end;
    // - inner ECPrivateKey scalar was in [1, n-2];
    // - inner ECPrivateKey public-key field (if present) cross-checked
    //   against derived public.
}

/// Cross-check: the public key recovered from SPKI matches the
/// public key derived from the PKCS#8 private scalar. This is the
/// real interop test — it proves both halves of the gmssl key pair
/// round-trip through `gmcrypto-core`.
#[test]
fn gmssl_pkcs8_and_spki_describe_the_same_key() {
    let priv_der =
        pem::decode(ENCRYPTED_PKCS8_PEM, "ENCRYPTED PRIVATE KEY").expect("PEM decode PKCS#8");
    let pub_der = pem::decode(SPKI_PEM, "PUBLIC KEY").expect("PEM decode SPKI");

    let privkey = pkcs8::decrypt(&priv_der, FIXTURE_PASSWORD).expect("PKCS#8 decrypt");
    let derived_pub = privkey.public_key();
    let pub_from_spki = spki::decode(&pub_der).expect("SPKI decode");

    assert!(
        bool::from(derived_pub.ct_eq(&pub_from_spki)),
        "SPKI public-key blob must match the public derived from the encrypted PKCS#8 scalar"
    );
}

/// Wrong password fails into a single uninformative `Failed`.
#[test]
fn gmssl_encrypted_pkcs8_wrong_password_fails() {
    let der = pem::decode(ENCRYPTED_PKCS8_PEM, "ENCRYPTED PRIVATE KEY").expect("PEM decode PKCS#8");
    assert!(matches!(
        pkcs8::decrypt(&der, b"not-the-password"),
        Err(pkcs8::Error::Failed)
    ));
}

/// Round-trip: encrypt a key with `gmcrypto-core`, decrypt with
/// `gmcrypto-core`. Sanity check that doesn't depend on the gmssl
/// fixture but exercises the same encrypt-decrypt code paths.
#[test]
fn rust_pkcs8_round_trip() {
    use crypto_bigint::U256;
    use gmcrypto_core::sm2::Sm2PrivateKey;

    let key = Sm2PrivateKey::new(U256::from_be_hex(
        "3945208F7B2144B13F36E38AC6D39F95889393692860B51A42FB81EF4DF7C5B8",
    ))
    .expect("valid d");

    let salt = [0xAB; 16];
    let iv = [0xCD; 16];
    let blob = pkcs8::encrypt(&key, b"correct horse", &salt, 8192, &iv).expect("encrypt");
    let recovered = pkcs8::decrypt(&blob, b"correct horse").expect("decrypt");

    assert!(bool::from(recovered.public_key().ct_eq(&key.public_key())));
}

/// Wrap with PEM and round-trip through the full file format.
#[test]
fn rust_pkcs8_pem_round_trip() {
    use crypto_bigint::U256;
    use gmcrypto_core::sm2::Sm2PrivateKey;

    let key = Sm2PrivateKey::new(U256::from_be_hex(
        "1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0",
    ))
    .expect("valid d");

    let salt = [0x11; 16];
    let iv = [0x22; 16];
    let der = pkcs8::encrypt(&key, b"pw", &salt, 8192, &iv).expect("encrypt");
    let pem_blob = pem::encode("ENCRYPTED PRIVATE KEY", &der);

    let der2 = pem::decode(&pem_blob, "ENCRYPTED PRIVATE KEY").expect("PEM decode round-trip");
    assert_eq!(der, der2, "PEM round-trip must be byte-identical");

    let recovered = pkcs8::decrypt(&der2, b"pw").expect("decrypt");
    assert!(bool::from(recovered.public_key().ct_eq(&key.public_key())));
}
