//! FIPS 140-3 Pairwise Consistency Tests (PCT)
//!
//! This module implements Pairwise Consistency Tests as required by FIPS 140-3 for
//! digital signature algorithms. PCT is a conditional self-test that must be performed
//! immediately after key generation to detect any corruption in the generated keypair.
//!
//! ## FIPS 140-3 Requirements
//!
//! According to FIPS 140-3 IG 10.3.A, a PCT for digital signature algorithms consists of:
//! 1. Signing a known test message with the newly generated secret key
//! 2. Verifying the signature with the corresponding public key
//! 3. If verification fails, the module must enter an error state
//!
//! ## Supported Algorithms
//!
//! - **ML-DSA** (FIPS 204): Module-Lattice-Based Digital Signature Algorithm
//! - **SLH-DSA** (FIPS 205): Stateless Hash-Based Digital Signature Algorithm
//! - **FN-DSA** (draft FIPS 206): FFT-over-NTRU-Lattice Digital Signature Algorithm
//! - **Ed25519** (RFC 8032): Edwards-Curve Digital Signature Algorithm (non-FIPS)
//! - **secp256k1** (SEC 2): ECDSA on secp256k1 curve (non-FIPS)
//!
//! ## Usage
//!
//! PCT functions are called automatically by the key generation functions when
//! the `fips-self-test` feature is enabled. They can also be called manually:
//!
//! ```no_run
//! use latticearc::primitives::pct::{pct_ml_dsa, PctError};
//! use latticearc::primitives::sig::ml_dsa::{MlDsaPublicKey, MlDsaSecretKey, MlDsaParameterSet};
//!
//! // After generating keys...
//! // let (pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa65)?;
//!
//! // Perform PCT
//! // pct_ml_dsa(&pk, &sk)?;
//! ```
//!
//! ## Security Considerations
//!
//! - PCT must be performed immediately after key generation, before the keys are used
//! - If PCT fails, the generated keys must not be used
//! - The test message is fixed to ensure deterministic testing

#![deny(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::panic)]

use thiserror::Error;

/// Fixed test message for PCT operations
///
/// This message is used for all PCT sign/verify operations.
/// Using a fixed message ensures consistent, deterministic testing.
pub const PCT_TEST_MESSAGE: &[u8] = b"FIPS PCT test";

/// Empty context for PCT operations (required by some signature APIs)
pub const PCT_EMPTY_CONTEXT: &[u8] = &[];

/// Error types for Pairwise Consistency Test operations
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PctError {
    /// Signing operation failed during PCT
    #[error("PCT signing failed: {0}")]
    SigningFailed(String),

    /// Verification operation failed during PCT
    #[error("PCT verification failed: {0}")]
    VerificationFailed(String),

    /// Signature verification returned false (key pair is inconsistent)
    #[error("PCT failed: signature verification returned false - key pair is inconsistent")]
    KeyPairInconsistent,

    /// Parameter mismatch between public and secret keys
    #[error("PCT failed: parameter mismatch between public and secret keys")]
    ParameterMismatch,
}

/// Result type for PCT operations
pub type PctResult<T> = Result<T, PctError>;

/// Enter module error state when PCT fails (FIPS 140-3 requirement)
///
/// When a Pairwise Consistency Test fails, the module must enter an error
/// state and refuse all subsequent cryptographic operations.
#[cfg(feature = "fips-self-test")]
fn enter_pct_error_state() {
    crate::primitives::self_test::set_module_error(
        crate::primitives::self_test::ModuleErrorCode::SelfTestFailure,
    );
}

/// No-op when FIPS self-test feature is not enabled
#[cfg(not(feature = "fips-self-test"))]
fn enter_pct_error_state() {}

/// Finalize a PCT: if verification passed return `Ok(())`, otherwise enter
/// error state and return `Err(PctError::KeyPairInconsistent)`.
fn pct_finalize(is_valid: bool) -> PctResult<()> {
    if is_valid {
        Ok(())
    } else {
        enter_pct_error_state();
        Err(PctError::KeyPairInconsistent)
    }
}

// =============================================================================
// ML-DSA Pairwise Consistency Test
// =============================================================================

/// Performs a Pairwise Consistency Test for ML-DSA keypairs
///
/// This function signs a fixed test message with the secret key and verifies
/// the signature with the public key. According to FIPS 140-3, this test must
/// pass before the keypair can be used for any cryptographic operations.
///
/// # Arguments
///
/// * `public_key` - The ML-DSA public key to test
/// * `secret_key` - The ML-DSA secret key to test
///
/// # Returns
///
/// * `Ok(())` - The keypair is consistent and passed PCT
/// * `Err(PctError)` - The keypair failed PCT and must not be used
///
/// # Errors
///
/// Returns `PctError::ParameterMismatch` if the keys have different parameter sets.
/// Returns `PctError::SigningFailed` if signing the test message fails.
/// Returns `PctError::VerificationFailed` if verification encounters an error.
/// Returns `PctError::KeyPairInconsistent` if verification returns false.
///
/// # Example
///
/// ```no_run
/// use latticearc::primitives::sig::ml_dsa::{generate_keypair, MlDsaParameterSet};
/// use latticearc::primitives::pct::pct_ml_dsa;
///
/// let (pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa65)?;
/// pct_ml_dsa(&pk, &sk)?;
/// // Keys are now validated and safe to use
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn pct_ml_dsa(
    public_key: &crate::primitives::sig::ml_dsa::MlDsaPublicKey,
    secret_key: &crate::primitives::sig::ml_dsa::MlDsaSecretKey,
) -> PctResult<()> {
    // Verify parameter sets match
    if public_key.parameter_set() != secret_key.parameter_set() {
        return Err(PctError::ParameterMismatch);
    }

    // Sign the test message
    let signature = secret_key
        .sign(PCT_TEST_MESSAGE, PCT_EMPTY_CONTEXT)
        .map_err(|e| PctError::SigningFailed(e.to_string()))?;

    // Verify the signature
    let is_valid = public_key
        .verify(PCT_TEST_MESSAGE, &signature, PCT_EMPTY_CONTEXT)
        .map_err(|e| PctError::VerificationFailed(e.to_string()))?;

    pct_finalize(is_valid)
}

// =============================================================================
// ML-KEM Pairwise Consistency Test
// =============================================================================

/// Performs a Pairwise Consistency Test on an ML-KEM keypair.
///
/// Encapsulates a shared secret to `keypair`'s public key, decapsulates the
/// resulting ciphertext with the same keypair's secret key, and verifies the
/// two shared secrets match. Per FIPS 140-3 IG 10.3.A the PCT must run on the
/// exact keypair being introduced, so this tests the caller's keypair — it
/// does not generate one.
///
/// # Arguments
///
/// * `keypair` - The ML-KEM decapsulation keypair to test
///
/// # Returns
///
/// * `Ok(())` - The keypair is consistent and passed PCT
/// * `Err(PctError)` - The keypair failed PCT and must not be used
///
/// # Errors
///
/// Returns `PctError::SigningFailed` if encapsulation fails.
/// Returns `PctError::VerificationFailed` if decapsulation fails.
/// Returns `PctError::KeyPairInconsistent` if the shared secrets don't match.
///
/// # Example
///
/// ```no_run
/// use latticearc::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
/// use latticearc::primitives::pct::pct_ml_kem;
///
/// let keypair = MlKem::generate_decapsulation_keypair(MlKemSecurityLevel::MlKem768)?;
/// pct_ml_kem(&keypair)?;
/// // Keypair is now validated and safe to use
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn pct_ml_kem(
    keypair: &crate::primitives::kem::ml_kem::MlKemDecapsulationKeyPair,
) -> PctResult<()> {
    use crate::primitives::kem::ml_kem::MlKem;
    use subtle::ConstantTimeEq;

    // Test the actual keypair the caller is about to
    // expose, not a freshly-generated sibling. FIPS 140-3 IG 10.3.A
    // requires the PCT to apply to the keypair being introduced —
    // testing a different keypair satisfies the wording but not the
    // intent. The other PCT helpers (`pct_ml_dsa`, `pct_slh_dsa`,
    // `pct_fn_dsa`) all already take the keypair; this brings ML-KEM
    // into parity. H1 wires this into
    // `generate_decapsulation_keypair` (the missing call site).

    // Encapsulate
    let (ss_encap, ct) = MlKem::encapsulate(keypair.public_key())
        .map_err(|e| PctError::SigningFailed(format!("ML-KEM encapsulation failed: {e}")))?;

    // Decapsulate against the SAME keypair we just encapsulated to
    let ss_decap = keypair
        .decapsulate(&ct)
        .map_err(|e| PctError::VerificationFailed(format!("ML-KEM decapsulation failed: {e}")))?;

    // Constant-time comparison
    let is_valid = bool::from(ss_encap.expose_secret().ct_eq(ss_decap.expose_secret()));
    pct_finalize(is_valid)
}

// =============================================================================
// SLH-DSA Pairwise Consistency Test
// =============================================================================

/// Performs a Pairwise Consistency Test for SLH-DSA keypairs
///
/// This function signs a fixed test message with the signing key and verifies
/// the signature with the verifying key. According to FIPS 140-3, this test must
/// pass before the keypair can be used for any cryptographic operations.
///
/// # Arguments
///
/// * `verifying_key` - The SLH-DSA verifying key (public key) to test
/// * `signing_key` - The SLH-DSA signing key (secret key) to test
///
/// # Returns
///
/// * `Ok(())` - The keypair is consistent and passed PCT
/// * `Err(PctError)` - The keypair failed PCT and must not be used
///
/// # Errors
///
/// Returns `PctError::ParameterMismatch` if the keys have different security levels.
/// Returns `PctError::SigningFailed` if signing the test message fails.
/// Returns `PctError::VerificationFailed` if verification encounters an error.
/// Returns `PctError::KeyPairInconsistent` if verification returns false.
///
/// # Example
///
/// ```no_run
/// use latticearc::primitives::sig::slh_dsa::{SigningKey, SlhDsaSecurityLevel};
/// use latticearc::primitives::pct::pct_slh_dsa;
///
/// let (sk, vk) = SigningKey::generate(SlhDsaSecurityLevel::Shake128s)?;
/// pct_slh_dsa(&vk, &sk)?;
/// // Keys are now validated and safe to use
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn pct_slh_dsa(
    verifying_key: &crate::primitives::sig::slh_dsa::VerifyingKey,
    signing_key: &crate::primitives::sig::slh_dsa::SigningKey,
) -> PctResult<()> {
    // Verify security levels match
    if verifying_key.security_level() != signing_key.security_level() {
        return Err(PctError::ParameterMismatch);
    }

    // Sign the test message (no context for PCT)
    let signature = signing_key
        .sign(PCT_TEST_MESSAGE, &[])
        .map_err(|e| PctError::SigningFailed(e.to_string()))?;

    // Verify the signature
    let is_valid = verifying_key
        .verify(PCT_TEST_MESSAGE, &signature, &[])
        .map_err(|e| PctError::VerificationFailed(e.to_string()))?;

    pct_finalize(is_valid)
}

// =============================================================================
// FN-DSA Pairwise Consistency Test
// =============================================================================

/// Performs a Pairwise Consistency Test for FN-DSA keypairs
///
/// This function signs a fixed test message with the signing key and verifies
/// the signature with the verifying key. According to FIPS 140-3, this test must
/// pass before the keypair can be used for any cryptographic operations.
///
/// # Arguments
///
/// * `verifying_key` - The FN-DSA verifying key (public key) to test
/// * `signing_key` - The FN-DSA signing key (secret key) to test
///
/// # Returns
///
/// * `Ok(())` - The keypair is consistent and passed PCT
/// * `Err(PctError)` - The keypair failed PCT and must not be used
///
/// # Errors
///
/// Returns `PctError::ParameterMismatch` if the keys have different security levels.
/// Returns `PctError::SigningFailed` if signing the test message fails.
/// Returns `PctError::VerificationFailed` if verification encounters an error.
/// Returns `PctError::KeyPairInconsistent` if verification returns false.
///
/// # Example
///
/// ```no_run
/// use latticearc::primitives::sig::fndsa::{KeyPair, FnDsaSecurityLevel};
/// use latticearc::primitives::pct::pct_fn_dsa;
/// use rand_core_0_6::OsRng;
///
/// let mut rng = OsRng;
/// let keypair = KeyPair::generate_with_rng(&mut rng, FnDsaSecurityLevel::Level512)?;
/// // Note: For FN-DSA, signing requires mutable access, so we use the keypair's sign method
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn pct_fn_dsa(
    verifying_key: &crate::primitives::sig::fndsa::VerifyingKey,
    signing_key: &mut crate::primitives::sig::fndsa::SigningKey,
) -> PctResult<()> {
    use rand_core_0_6::OsRng;

    // Verify security levels match
    if verifying_key.security_level() != signing_key.security_level() {
        return Err(PctError::ParameterMismatch);
    }

    // Sign the test message (FN-DSA requires an RNG for signing)
    let mut rng = OsRng;
    let signature = signing_key
        .sign_with_rng(&mut rng, PCT_TEST_MESSAGE)
        .map_err(|e| PctError::SigningFailed(e.to_string()))?;

    // Verify the signature
    let is_valid = verifying_key
        .verify(PCT_TEST_MESSAGE, &signature)
        .map_err(|e| PctError::VerificationFailed(e.to_string()))?;

    pct_finalize(is_valid)
}

/// Performs a Pairwise Consistency Test for an FN-DSA KeyPair
///
/// This is a convenience function that performs PCT on a complete FN-DSA KeyPair
/// structure. It internally calls `pct_fn_dsa` with the keypair's components.
///
/// # Arguments
///
/// * `keypair` - The FN-DSA keypair to test (mutable because signing requires it)
///
/// # Returns
///
/// * `Ok(())` - The keypair is consistent and passed PCT
/// * `Err(PctError)` - The keypair failed PCT and must not be used
///
/// # Errors
///
/// Returns errors from the underlying `pct_fn_dsa` function.
///
/// # Example
///
/// ```no_run
/// use latticearc::primitives::sig::fndsa::{KeyPair, FnDsaSecurityLevel};
/// use latticearc::primitives::pct::pct_fn_dsa_keypair;
/// use rand_core_0_6::OsRng;
///
/// let mut rng = OsRng;
/// let mut keypair = KeyPair::generate_with_rng(&mut rng, FnDsaSecurityLevel::Level512)?;
/// pct_fn_dsa_keypair(&mut keypair)?;
/// // Keypair is now validated and safe to use
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn pct_fn_dsa_keypair(keypair: &mut crate::primitives::sig::fndsa::KeyPair) -> PctResult<()> {
    use rand_core_0_6::OsRng;

    // Sign the test message
    let mut rng = OsRng;
    let signature = keypair
        .sign_with_rng(&mut rng, PCT_TEST_MESSAGE)
        .map_err(|e| PctError::SigningFailed(e.to_string()))?;

    // Verify the signature
    let is_valid = keypair
        .verify(PCT_TEST_MESSAGE, &signature)
        .map_err(|e| PctError::VerificationFailed(e.to_string()))?;

    pct_finalize(is_valid)
}

// =============================================================================
// Ed25519 Pairwise Consistency Test (non-FIPS only)
// =============================================================================

/// Performs a Pairwise Consistency Test for Ed25519 keypairs (non-FIPS only).
///
/// This function signs a fixed test message with the secret key and verifies
/// the signature with the public key. While Ed25519 is not a FIPS-approved
/// algorithm, PCT ensures keypair correctness after generation.
///
/// # Arguments
///
/// * `keypair` - The Ed25519 keypair to test
///
/// # Returns
///
/// * `Ok(())` - The keypair is consistent and passed PCT
/// * `Err(PctError)` - The keypair failed PCT and must not be used
///
/// # Errors
///
/// Returns `PctError::SigningFailed` if signing the test message fails.
/// Returns `PctError::VerificationFailed` if verification encounters an error.
///
/// Note: available in both FIPS and non-FIPS builds because the Ed25519
/// primitives wrapper (which calls this PCT during keypair generation) is
/// used by hybrid signatures and convenience APIs in all feature modes.
pub fn pct_ed25519(keypair: &crate::primitives::ec::ed25519::Ed25519KeyPair) -> PctResult<()> {
    use crate::primitives::ec::traits::{EcKeyPair, EcSignature};

    // mirror the pct_ml_dsa / pct_slh_dsa
    // pattern — explicit Ok(false) handling and pct_finalize so a
    // genuine Ed25519 pairwise inconsistency enters the FIPS 140-3 IG
    // 10.3.A error state instead of returning an ad-hoc error code.
    // `Ed25519KeyPair::sign` is fallible (validate_signature_size); the
    // 24-byte PCT message is well under the cap, so this path never
    // trips the gate, but using `?` keeps the surface honest if the cap
    // is ever lowered.
    let signature =
        keypair.sign(PCT_TEST_MESSAGE).map_err(|e| PctError::SigningFailed(e.to_string()))?;

    let verify_result = crate::primitives::ec::ed25519::Ed25519Signature::verify(
        &keypair.public_key_bytes(),
        PCT_TEST_MESSAGE,
        &signature,
    );
    let is_valid = match verify_result {
        Ok(()) => true,
        Err(e) => {
            tracing::debug!(error = %e, "Ed25519 PCT verify rejected");
            false
        }
    };
    pct_finalize(is_valid)
}

// =============================================================================
// Secp256k1 Pairwise Consistency Test (non-FIPS only)
// =============================================================================

/// Performs a Pairwise Consistency Test for secp256k1 keypairs (non-FIPS only).
///
/// This function signs a fixed test message with the secret key and verifies
/// the signature with the public key. While secp256k1 is not a FIPS-approved
/// algorithm, PCT ensures keypair correctness after generation.
///
/// # Arguments
///
/// * `keypair` - The secp256k1 keypair to test
///
/// # Returns
///
/// * `Ok(())` - The keypair is consistent and passed PCT
/// * `Err(PctError)` - The keypair failed PCT and must not be used
///
/// # Errors
///
/// Returns `PctError::SigningFailed` if signing the test message fails.
/// Returns `PctError::VerificationFailed` if verification encounters an error.
#[cfg(not(feature = "fips"))]
pub fn pct_secp256k1(
    keypair: &crate::primitives::ec::secp256k1::Secp256k1KeyPair,
) -> PctResult<()> {
    use crate::primitives::ec::traits::{EcKeyPair, EcSignature};

    // align with pct_ml_dsa / pct_slh_dsa via
    // pct_finalize so genuine pairwise inconsistencies enter the FIPS
    // 140-3 IG 10.3.A error state instead of producing an ad-hoc error
    // code that callers downstream of `enter_pct_error_state` never see.
    let signature =
        keypair.sign(PCT_TEST_MESSAGE).map_err(|e| PctError::SigningFailed(e.to_string()))?;

    let verify_result = crate::primitives::ec::secp256k1::Secp256k1Signature::verify(
        &keypair.public_key_bytes(),
        PCT_TEST_MESSAGE,
        &signature,
    );
    let is_valid = match verify_result {
        Ok(()) => true,
        Err(e) => {
            tracing::debug!(error = %e, "secp256k1 PCT verify rejected");
            false
        }
    };
    pct_finalize(is_valid)
}

// =============================================================================
// Tests
// =============================================================================

#[cfg(test)]
#[expect(clippy::expect_used, reason = "Tests use expect for simplicity")]
mod tests {
    use super::*;

    #[test]
    fn test_pct_ml_kem_768_passes() {
        use crate::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
        let keypair = MlKem::generate_decapsulation_keypair(MlKemSecurityLevel::MlKem768)
            .expect("keygen should succeed under test conditions");
        // PCT now takes the keypair, not the level.
        // `generate_decapsulation_keypair` ALSO runs PCT internally
        // post-fix, so this is technically redundant —
        // we re-run it to assert the externally-callable PCT passes
        // on a known-good keypair.
        let result = pct_ml_kem(&keypair);
        assert!(result.is_ok(), "PCT should pass for ML-KEM-768");
    }

    #[test]
    fn test_pct_ml_kem_1024_passes() {
        use crate::primitives::kem::ml_kem::{MlKem, MlKemSecurityLevel};
        let keypair = MlKem::generate_decapsulation_keypair(MlKemSecurityLevel::MlKem1024)
            .expect("keygen should succeed under test conditions");
        let result = pct_ml_kem(&keypair);
        assert!(result.is_ok(), "PCT should pass for ML-KEM-1024");
    }

    #[test]
    fn test_pct_ml_dsa_44_passes() {
        use crate::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};

        let (pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa44).expect("Key generation failed");
        let result = pct_ml_dsa(&pk, &sk);
        assert!(result.is_ok(), "PCT should pass for valid ML-DSA-44 keypair");
    }

    #[test]
    fn test_pct_ml_dsa_65_passes() {
        use crate::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};

        let (pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa65).expect("Key generation failed");
        let result = pct_ml_dsa(&pk, &sk);
        assert!(result.is_ok(), "PCT should pass for valid ML-DSA-65 keypair");
    }

    #[test]
    fn test_pct_ml_dsa_87_passes() {
        use crate::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};

        let (pk, sk) = generate_keypair(MlDsaParameterSet::MlDsa87).expect("Key generation failed");
        let result = pct_ml_dsa(&pk, &sk);
        assert!(result.is_ok(), "PCT should pass for valid ML-DSA-87 keypair");
    }

    #[test]
    fn test_pct_ml_dsa_mismatched_keys_fails() {
        use crate::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};

        let (pk1, _sk1) =
            generate_keypair(MlDsaParameterSet::MlDsa44).expect("Key generation failed");
        let (_pk2, sk2) =
            generate_keypair(MlDsaParameterSet::MlDsa44).expect("Key generation failed");

        // Use public key from one keypair with secret key from another
        let result = pct_ml_dsa(&pk1, &sk2);
        // Clean up global error state set by enter_pct_error_state() to avoid
        // poisoning other tests running in the same process
        #[cfg(feature = "fips-self-test")]
        crate::primitives::self_test::restore_operational_state();
        assert!(
            matches!(result, Err(PctError::KeyPairInconsistent)),
            "PCT should fail for mismatched keys"
        );
    }

    #[test]
    fn test_pct_ml_dsa_parameter_mismatch_fails() {
        use crate::primitives::sig::ml_dsa::{MlDsaParameterSet, generate_keypair};

        let (pk44, _) =
            generate_keypair(MlDsaParameterSet::MlDsa44).expect("Key generation failed");
        let (_, sk65) =
            generate_keypair(MlDsaParameterSet::MlDsa65).expect("Key generation failed");

        // Different parameter sets should fail
        let result = pct_ml_dsa(&pk44, &sk65);
        assert!(
            matches!(result, Err(PctError::ParameterMismatch)),
            "PCT should fail for parameter mismatch"
        );
    }

    #[test]
    fn test_pct_slh_dsa_shake128s_passes() {
        use crate::primitives::sig::slh_dsa::{SigningKey, SlhDsaSecurityLevel};

        let (sk, vk) =
            SigningKey::generate(SlhDsaSecurityLevel::Shake128s).expect("Key generation failed");
        let result = pct_slh_dsa(&vk, &sk);
        assert!(result.is_ok(), "PCT should pass for valid SLH-DSA-SHAKE-128s keypair");
    }

    #[test]
    fn test_pct_slh_dsa_shake192s_passes() {
        use crate::primitives::sig::slh_dsa::{SigningKey, SlhDsaSecurityLevel};

        let (sk, vk) =
            SigningKey::generate(SlhDsaSecurityLevel::Shake192s).expect("Key generation failed");
        let result = pct_slh_dsa(&vk, &sk);
        assert!(result.is_ok(), "PCT should pass for valid SLH-DSA-SHAKE-192s keypair");
    }

    #[test]
    fn test_pct_slh_dsa_shake256s_passes() {
        use crate::primitives::sig::slh_dsa::{SigningKey, SlhDsaSecurityLevel};

        let (sk, vk) =
            SigningKey::generate(SlhDsaSecurityLevel::Shake256s).expect("Key generation failed");
        let result = pct_slh_dsa(&vk, &sk);
        assert!(result.is_ok(), "PCT should pass for valid SLH-DSA-SHAKE-256s keypair");
    }

    #[test]
    fn test_pct_slh_dsa_mismatched_keys_fails() {
        use crate::primitives::sig::slh_dsa::{SigningKey, SlhDsaSecurityLevel};

        let (sk1, _vk1) =
            SigningKey::generate(SlhDsaSecurityLevel::Shake128s).expect("Key generation failed");
        let (_sk2, vk2) =
            SigningKey::generate(SlhDsaSecurityLevel::Shake128s).expect("Key generation failed");

        // Use verifying key from one keypair with signing key from another
        let result = pct_slh_dsa(&vk2, &sk1);
        // Clean up global error state set by enter_pct_error_state() to avoid
        // poisoning other tests running in the same process
        #[cfg(feature = "fips-self-test")]
        crate::primitives::self_test::restore_operational_state();
        assert!(
            matches!(result, Err(PctError::KeyPairInconsistent)),
            "PCT should fail for mismatched keys"
        );
    }

    #[test]
    fn test_pct_slh_dsa_parameter_mismatch_fails() {
        use crate::primitives::sig::slh_dsa::{SigningKey, SlhDsaSecurityLevel};

        let (sk128, _) =
            SigningKey::generate(SlhDsaSecurityLevel::Shake128s).expect("Key generation failed");
        let (_, vk256) =
            SigningKey::generate(SlhDsaSecurityLevel::Shake256s).expect("Key generation failed");

        // Different security levels should fail
        let result = pct_slh_dsa(&vk256, &sk128);
        assert!(
            matches!(result, Err(PctError::ParameterMismatch)),
            "PCT should fail for parameter mismatch"
        );
    }

    #[test]
    fn test_pct_fn_dsa_512_passes() {
        std::thread::Builder::new()
            .stack_size(32 * 1024 * 1024)
            .spawn(|| {
                use crate::primitives::sig::fndsa::{FnDsaSecurityLevel, KeyPair};
                use rand_core_0_6::OsRng;
                let mut rng = OsRng;
                let mut keypair =
                    KeyPair::generate_with_rng(&mut rng, FnDsaSecurityLevel::Level512)
                        .expect("Key generation failed");
                let result = pct_fn_dsa_keypair(&mut keypair);
                assert!(result.is_ok(), "PCT should pass for valid FN-DSA-512 keypair");
            })
            .expect("Thread spawn failed")
            .join()
            .expect("Thread join failed");
    }

    #[test]
    fn test_pct_fn_dsa_1024_passes() {
        std::thread::Builder::new()
            .stack_size(32 * 1024 * 1024)
            .spawn(|| {
                use crate::primitives::sig::fndsa::{FnDsaSecurityLevel, KeyPair};
                use rand_core_0_6::OsRng;
                let mut rng = OsRng;
                let mut keypair =
                    KeyPair::generate_with_rng(&mut rng, FnDsaSecurityLevel::Level1024)
                        .expect("Key generation failed");
                let result = pct_fn_dsa_keypair(&mut keypair);
                assert!(result.is_ok(), "PCT should pass for valid FN-DSA-1024 keypair");
            })
            .expect("Thread spawn failed")
            .join()
            .expect("Thread join failed");
    }

    #[test]
    fn test_pct_fn_dsa_with_separate_keys_passes() {
        std::thread::Builder::new()
            .stack_size(32 * 1024 * 1024)
            .spawn(|| {
                use crate::primitives::sig::fndsa::{FnDsaSecurityLevel, KeyPair};
                use rand_core_0_6::OsRng;
                let mut rng = OsRng;
                let keypair = KeyPair::generate_with_rng(&mut rng, FnDsaSecurityLevel::Level512)
                    .expect("Key generation failed");

                // Get verifying key from the keypair
                let vk = keypair.verifying_key().clone();

                // Recreate signing key from bytes
                let sk_bytes = keypair.signing_key().to_bytes();
                let mut sk = crate::primitives::sig::fndsa::SigningKey::from_bytes(
                    &sk_bytes,
                    FnDsaSecurityLevel::Level512,
                )
                .expect("SigningKey reconstruction failed");

                let result = pct_fn_dsa(&vk, &mut sk);
                assert!(result.is_ok(), "PCT should pass for valid FN-DSA keypair components");
            })
            .expect("Thread spawn failed")
            .join()
            .expect("Thread join failed");
    }

    #[test]
    fn test_pct_fn_dsa_parameter_mismatch_fails() {
        std::thread::Builder::new()
            .stack_size(32 * 1024 * 1024)
            .spawn(|| {
                use crate::primitives::sig::fndsa::{FnDsaSecurityLevel, KeyPair};
                use rand_core_0_6::OsRng;
                let mut rng = OsRng;
                let keypair512 = KeyPair::generate_with_rng(&mut rng, FnDsaSecurityLevel::Level512)
                    .expect("Key generation failed");
                let keypair1024 =
                    KeyPair::generate_with_rng(&mut rng, FnDsaSecurityLevel::Level1024)
                        .expect("Key generation failed");

                // Get verifying key from 1024 and signing key from 512
                let vk1024 = keypair1024.verifying_key().clone();
                let sk_bytes = keypair512.signing_key().to_bytes();
                let mut sk512 = crate::primitives::sig::fndsa::SigningKey::from_bytes(
                    &sk_bytes,
                    FnDsaSecurityLevel::Level512,
                )
                .expect("SigningKey reconstruction failed");

                let result = pct_fn_dsa(&vk1024, &mut sk512);
                assert!(
                    matches!(result, Err(PctError::ParameterMismatch)),
                    "PCT should fail for parameter mismatch"
                );
            })
            .expect("Thread spawn failed")
            .join()
            .expect("Thread join failed");
    }

    #[test]
    fn test_pct_error_display_fails() {
        let errors = vec![
            PctError::SigningFailed("test error".to_string()),
            PctError::VerificationFailed("test error".to_string()),
            PctError::KeyPairInconsistent,
            PctError::ParameterMismatch,
        ];

        for error in errors {
            let display = format!("{error}");
            assert!(!display.is_empty(), "Error display should not be empty");
        }
    }

    #[test]
    fn test_pct_constants_succeeds() {
        assert_eq!(PCT_TEST_MESSAGE, b"FIPS PCT test");
        assert!(PCT_EMPTY_CONTEXT.is_empty());
    }
}
