//! `DPoP` (Demonstrating Proof-of-Possession) key management and proof
//! generation.
//!
//! Implements RFC 9449 for `DPoP` token binding.
//!
//! # Security: Zeroization (SEC-001)
//!
//! This module implements secure memory handling for cryptographic key
//! material:
//!
//! - **Automatic zeroization**: Private key bytes are securely erased from
//!   memory when `DPoPKeyPair` is dropped, using compiler-barrier-protected
//!   volatile writes that cannot be optimized away.
//!
//! - **Defense in depth**: The key material is stored in a `SecretBox` which
//!   provides an additional layer of protection against accidental exposure via
//!   Debug, Display, or serialization.
//!
//! - **Extensibility**: The [`ZeroizeStrategy`] trait allows callers to
//!   implement custom zeroization behavior if needed (e.g., for HSM-backed keys
//!   or custom secure memory allocators).
//!
//! ## Limitations
//!
//! - **No guarantee against memory copies**: The Rust compiler, OS, or hardware
//!   may create copies of sensitive data (e.g., during register spills, page
//!   swaps, or speculative execution). This is a fundamental limitation of
//!   software-based zeroization.
//!
//! - **Release builds recommended**: Debug builds may retain additional copies
//!   of data on the stack for debugging purposes.
//!
//! ## References
//!
//! - [OWASP Cryptographic Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html)
//! - [zeroize crate documentation](https://docs.rs/zeroize)
//! - [RFC 9449: OAuth 2.0 Demonstrating Proof of Possession](https://datatracker.ietf.org/doc/html/rfc9449)

// Platform-specific time imports
#[cfg(not(target_arch = "wasm32"))]
use std::time::{SystemTime, UNIX_EPOCH};

use p256::ecdsa::signature::Signer;
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
use p256::pkcs8::DecodePrivateKey;
#[cfg(feature = "keypair-serialization")]
use p256::pkcs8::{EncodePrivateKey, LineEnding};
use secrecy::{ExposeSecret, SecretBox};
use serde::{Deserialize, Serialize};
#[cfg(feature = "keypair-serialization")]
use serde::{Deserializer, Serializer};
use zeroize::{Zeroize, Zeroizing};

use crate::constants::DPOP_PROOF_MAX_LIFETIME_SECONDS;
use crate::error::LastIDError;

/// Size of P-256 scalar in bytes (32 bytes = 256 bits).
const P256_SCALAR_SIZE: usize = 32;

/// `DPoP` key pair for proof-of-possession authentication.
///
/// Wraps a P-256 ECDSA key pair used for generating `DPoP` proofs per RFC 9449.
/// The private key is automatically zeroized when dropped.
///
/// # Serialization
///
/// This struct implements `Serialize` and `Deserialize` for persistence across
/// serverless invocations or process restarts. The serialized form contains the
/// PEM-encoded private key, from which the verifying key and JWK thumbprint are
/// derived on deserialization.
///
/// **Security Warning**: The serialized form contains the private key. Ensure
/// proper encryption at rest when storing serialized keypairs (e.g., AWS KMS,
/// `HashiCorp` Vault, or encrypted environment variables).
///
/// # Example
///
/// ```rust,no_run
/// use lastid_sdk::crypto::DPoPKeyPair;
///
/// # fn example() -> Result<(), lastid_sdk::LastIDError> {
/// // Load from PEM file
/// let pem = std::fs::read("private_key.pem")?;
/// let keypair = DPoPKeyPair::from_pem(&pem)?;
///
/// // Generate a proof for an HTTP request
/// let proof = keypair.create_dpop_proof(
///     "POST",
///     "https://api.example.com/token",
///     None,
///     Some("my-client-id"),
/// )?;
/// # Ok(())
/// # }
/// ```
///
/// # Serialization (requires `keypair-serialization` feature)
#[cfg_attr(feature = "keypair-serialization", doc = "```rust,no_run")]
#[cfg_attr(not(feature = "keypair-serialization"), doc = "```rust,ignore")]
/// use lastid_sdk::crypto::DPoPKeyPair;
///
/// # fn example() -> Result<(), lastid_sdk::LastIDError> {
/// let keypair = DPoPKeyPair::generate()?;
///
/// // Serialize for persistence (e.g., serverless cold start)
/// let serialized = serde_json::to_string(&keypair).unwrap();
///
/// // Deserialize later
/// let restored: DPoPKeyPair = serde_json::from_str(&serialized).unwrap();
/// assert_eq!(keypair.thumbprint(), restored.thumbprint());
/// # Ok(())
/// # }
/// ```
pub struct DPoPKeyPair {
    signing_key: SecretBox<SigningKeyWrapper>,
    verifying_key: VerifyingKey,
    jwk_thumbprint: String,
}

/// Serialization helper for `DPoPKeyPair`.
///
/// Serializes only the private key PEM; the verifying key and thumbprint
/// are derived on deserialization.
///
/// # Security (SEC-002)
///
/// Keypair serialization is gated behind the `keypair-serialization` feature
/// to prevent accidental exposure of private keys. Enable only when needed
/// for serverless persistence.
#[cfg(feature = "keypair-serialization")]
#[derive(Serialize, Deserialize)]
struct DPoPKeyPairSerialized {
    /// PEM-encoded P-256 private key (PKCS#8 format)
    private_key_pem: String,
}

/// SEC-002: Keypair serialization requires explicit opt-in via
/// `keypair-serialization` feature.
///
/// **WARNING**: Serialized keypairs contain private keys. Ensure encrypted
/// storage.
#[cfg(feature = "keypair-serialization")]
impl Serialize for DPoPKeyPair {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // Export the private key to PEM format
        let pem = self
            .signing_key
            .expose_secret()
            .signing_key()
            .to_pkcs8_pem(LineEnding::LF)
            .map_err(serde::ser::Error::custom)?;

        let helper = DPoPKeyPairSerialized {
            private_key_pem: pem.to_string(),
        };
        helper.serialize(serializer)
    }
}

/// SEC-002: Keypair deserialization requires explicit opt-in via
/// `keypair-serialization` feature.
#[cfg(feature = "keypair-serialization")]
impl<'de> Deserialize<'de> for DPoPKeyPair {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let helper = DPoPKeyPairSerialized::deserialize(deserializer)?;

        // Reconstruct keypair from PEM
        Self::from_pem(helper.private_key_pem.as_bytes()).map_err(serde::de::Error::custom)
    }
}

// =============================================================================
// ZEROIZATION STRATEGY TRAIT (SEC-001)
// =============================================================================

/// Strategy trait for custom zeroization behavior.
///
/// This trait allows callers to implement custom zeroization strategies for
/// specialized use cases such as:
///
/// - HSM-backed keys that require specific cleanup procedures
/// - Custom secure memory allocators (e.g., mlock'd pages)
/// - Hardware security modules with dedicated key destruction APIs
///
/// # Default Implementation
///
/// The default [`DefaultZeroizeStrategy`] uses the `zeroize` crate's
/// secure memory clearing, which:
///
/// 1. Uses volatile writes that cannot be optimized away by the compiler
/// 2. Inserts memory barriers to prevent reordering
/// 3. Overwrites memory with zeros (not random data)
///
/// # Security Considerations
///
/// - Zeroization happens synchronously and blocks until complete
/// - No guarantees against copies made by the OS (page swaps) or CPU
///   (speculative execution)
/// - For maximum security, consider process-level memory protection (mlock,
///   etc.)
///
/// # Example
///
/// ```rust,ignore
/// use lastid_sdk::crypto::ZeroizeStrategy;
///
/// struct HsmZeroizeStrategy;
///
/// impl ZeroizeStrategy for HsmZeroizeStrategy {
///     fn zeroize_key_bytes(&self, bytes: &mut [u8; 32]) {
///         // Custom HSM cleanup
///         hsm_destroy_key(bytes);
///         // Then zero the local copy
///         bytes.zeroize();
///     }
/// }
/// ```
pub trait ZeroizeStrategy: Send + Sync {
    /// Securely erase key material from memory.
    ///
    /// Implementations MUST ensure the bytes are overwritten before returning.
    /// The default implementation uses volatile writes with memory barriers.
    fn zeroize_key_bytes(&self, bytes: &mut [u8; P256_SCALAR_SIZE]);
}

/// Default zeroization strategy using the `zeroize` crate.
///
/// This is the recommended strategy for most use cases. It:
///
/// 1. Overwrites all 32 bytes of the P-256 scalar with zeros
/// 2. Uses volatile writes that cannot be optimized away
/// 3. Inserts memory barriers to prevent instruction reordering
///
/// # Security Properties
///
/// - **Compiler-safe**: Uses `core::ptr::write_volatile` internally
/// - **Optimizer-resistant**: Memory barriers prevent dead store elimination
/// - **Deterministic**: Always zeros (vs. random overwrite which requires RNG)
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultZeroizeStrategy;

impl ZeroizeStrategy for DefaultZeroizeStrategy {
    fn zeroize_key_bytes(&self, bytes: &mut [u8; P256_SCALAR_SIZE]) {
        bytes.zeroize();
    }
}

// =============================================================================
// SIGNING KEY WRAPPER (SEC-001, SEC-002, SEC-003)
// =============================================================================

/// Wrapper for `SigningKey` with proper zeroization.
///
/// # Security (SEC-001, SEC-002, SEC-003)
///
/// - **SEC-001**: Proper zeroization - key bytes are zeroed on drop using
///   volatile writes, not replaced with random data
/// - **SEC-002**: No `Default` impl - keys must be explicitly generated with
///   `OsRng`
/// - **SEC-003**: Defense in depth - stores raw bytes for reliable zeroization
///
/// # Implementation Details
///
/// We store both the `SigningKey` and its raw scalar bytes because:
///
/// 1. `p256::ecdsa::SigningKey` implements `ZeroizeOnDrop` but not `Zeroize`
/// 2. The `secrecy::SecretBox` requires `Zeroize` for its type parameter
/// 3. By storing the raw bytes, we can implement reliable `Zeroize`
///
/// The raw bytes are kept in sync with the `SigningKey` and are zeroized
/// when `Zeroize::zeroize()` is called. The `SigningKey` itself will also
/// be zeroized when dropped (via its `ZeroizeOnDrop` implementation).
struct SigningKeyWrapper {
    /// The actual signing key for cryptographic operations.
    key: SigningKey,
    /// Raw scalar bytes for reliable zeroization.
    /// Uses `Zeroizing` wrapper which zeros on drop.
    scalar_bytes: Zeroizing<[u8; P256_SCALAR_SIZE]>,
}

impl SigningKeyWrapper {
    /// Create a new wrapper from a signing key.
    ///
    /// Extracts and stores the raw scalar bytes for reliable zeroization.
    fn new(key: SigningKey) -> Self {
        // Extract the scalar bytes from the key
        let bytes = key.to_bytes();
        let mut scalar_bytes = [0u8; P256_SCALAR_SIZE];
        scalar_bytes.copy_from_slice(&bytes);

        Self {
            key,
            scalar_bytes: Zeroizing::new(scalar_bytes),
        }
    }

    /// Get a reference to the underlying signing key.
    const fn signing_key(&self) -> &SigningKey {
        &self.key
    }
}

impl Clone for SigningKeyWrapper {
    fn clone(&self) -> Self {
        Self::new(self.key.clone())
    }
}

// SEC-002: No Default impl - keys must be generated explicitly with generate()
// This prevents accidental use of weak RNG for cryptographic key generation.

/// SEC-001: Proper Zeroize implementation.
///
/// This implementation properly zeros the key material:
///
/// 1. Zeros the raw scalar bytes using volatile writes
/// 2. The underlying `SigningKey` will also be zeroized on drop
///
/// # Why This Works
///
/// The `zeroize` crate uses `core::ptr::write_volatile` with memory barriers,
/// which guarantees the compiler cannot optimize away the zeroing operation.
/// This is the industry-standard approach used by ring, ed25519-dalek, and
/// other security-focused cryptographic libraries.
///
/// # Previous Issue (FIXED)
///
/// The previous implementation replaced the key with random data, which:
/// - Required calling the RNG during cleanup (could fail or block)
/// - Did not actually remove the original key material from memory
/// - Was not deterministic or verifiable
impl Zeroize for SigningKeyWrapper {
    fn zeroize(&mut self) {
        // Zero the raw scalar bytes using the zeroize crate
        // This uses volatile writes that cannot be optimized away
        self.scalar_bytes.zeroize();

        // Note: The SigningKey itself has ZeroizeOnDrop, so it will be
        // zeroized when this struct is dropped. However, we've already
        // zeroized our copy of the bytes above.
    }
}

impl Drop for SigningKeyWrapper {
    fn drop(&mut self) {
        // Ensure zeroization happens on drop
        self.zeroize();
    }
}

impl secrecy::CloneableSecret for SigningKeyWrapper {}

impl DPoPKeyPair {
    /// Load a `DPoP` key pair from PEM-encoded private key.
    ///
    /// The PEM should contain a PKCS#8-encoded P-256 private key.
    ///
    /// # Errors
    ///
    /// Returns `LastIDError::Crypto` if the PEM is invalid or not a P-256 key.
    pub fn from_pem(pem: &[u8]) -> Result<Self, LastIDError> {
        let pem_str = std::str::from_utf8(pem)
            .map_err(|e| LastIDError::crypto(format!("Invalid PEM: {e}")))?;

        let signing_key = SigningKey::from_pkcs8_pem(pem_str)
            .map_err(|e| LastIDError::crypto(format!("Failed to parse private key: {e}")))?;

        let verifying_key = *signing_key.verifying_key();
        let jwk_thumbprint = Self::compute_jwk_thumbprint(&verifying_key);

        Ok(Self {
            signing_key: SecretBox::new(Box::new(SigningKeyWrapper::new(signing_key))),
            verifying_key,
            jwk_thumbprint,
        })
    }

    /// Generate a new random `DPoP` key pair.
    ///
    /// Creates a P-256 ECDSA key pair for `DPoP` proof-of-possession
    /// authentication. This is used for OAuth token acquisition and request
    /// signing.
    ///
    /// # Platform Support
    ///
    /// - **Native**: Uses `OsRng` for secure random number generation
    /// - **WASM**: Uses `getrandom` with the `js` feature for browser RNG
    ///
    /// # Errors
    ///
    /// Returns `LastIDError::Crypto` if key generation fails.
    pub fn generate() -> Result<Self, LastIDError> {
        use rand::rngs::OsRng;

        let signing_key = SigningKey::random(&mut OsRng);
        let verifying_key = *signing_key.verifying_key();
        let jwk_thumbprint = Self::compute_jwk_thumbprint(&verifying_key);

        Ok(Self {
            signing_key: SecretBox::new(Box::new(SigningKeyWrapper::new(signing_key))),
            verifying_key,
            jwk_thumbprint,
        })
    }

    /// Create a `DPoP` proof JWT.
    ///
    /// Generates a signed JWT proving possession of the private key for the
    /// specified HTTP method and URI, per RFC 9449.
    ///
    /// # Arguments
    ///
    /// * `http_method` - HTTP method (e.g., "POST", "GET")
    /// * `http_uri` - Target URI (scheme + host + path, no query/fragment)
    /// * `access_token` - Optional access token for `ath` claim binding
    /// * `subject` - Optional subject identifier (typically `client_id`)
    ///
    /// # Returns
    ///
    /// A signed JWT string suitable for the `DPoP` HTTP header.
    ///
    /// # Errors
    ///
    /// Returns `LastIDError::Crypto` if signing fails.
    pub fn create_dpop_proof(
        &self,
        http_method: &str,
        http_uri: &str,
        access_token: Option<&str>,
        subject: Option<&str>,
    ) -> Result<String, LastIDError> {
        let now = current_timestamp_secs()?;

        // Generate unique JWT ID
        let jti = uuid::Uuid::new_v4().to_string();

        // Compute access token hash if provided
        let ath = access_token.map(|token| {
            use sha2::{Digest, Sha256};
            let hash = Sha256::digest(token.as_bytes());
            base64_url_encode(&hash)
        });

        // Safe: Unix timestamp fits in i64 until year 292 billion
        #[allow(clippy::cast_possible_wrap)]
        let iat = now as i64;

        // SEC-006: Add expiration claim (300s max lifetime per RFC 9449)
        // This limits the replay attack window for DPoP proofs.
        #[allow(clippy::cast_possible_wrap)]
        let exp = (now + DPOP_PROOF_MAX_LIFETIME_SECONDS) as i64;

        let claims = DPoPProof {
            jti,
            htm: http_method.to_uppercase(),
            htu: http_uri.to_string(),
            iat,
            exp,
            sub: subject.map(String::from),
            ath,
        };

        Ok(self.sign_jwt(&claims))
    }

    /// Get the JWK thumbprint for this key pair.
    #[must_use]
    pub fn thumbprint(&self) -> &str {
        &self.jwk_thumbprint
    }

    /// Get the public key as JWK.
    #[must_use]
    pub fn public_jwk(&self) -> serde_json::Value {
        let point = self.verifying_key.to_encoded_point(false);
        let x = base64_url_encode(point.x().unwrap());
        let y = base64_url_encode(point.y().unwrap());

        serde_json::json!({
            "kty": "EC",
            "crv": "P-256",
            "x": x,
            "y": y
        })
    }

    /// Compute the JWK thumbprint per RFC 7638.
    fn compute_jwk_thumbprint(verifying_key: &VerifyingKey) -> String {
        use sha2::{Digest, Sha256};

        let point = verifying_key.to_encoded_point(false);
        let x = base64_url_encode(point.x().unwrap());
        let y = base64_url_encode(point.y().unwrap());

        // Canonical JWK representation per RFC 7638
        let canonical = format!(r#"{{"crv":"P-256","kty":"EC","x":"{x}","y":"{y}"}}"#);

        let hash = Sha256::digest(canonical.as_bytes());
        base64_url_encode(&hash)
    }

    /// Sign a JWT with the private key.
    fn sign_jwt(&self, claims: &DPoPProof) -> String {
        // Create header
        let header = serde_json::json!({
            "typ": "dpop+jwt",
            "alg": "ES256",
            "jwk": self.public_jwk()
        });

        let header_b64 = base64_url_encode(&serde_json::to_vec(&header).unwrap());
        let claims_b64 = base64_url_encode(&serde_json::to_vec(claims).unwrap());

        let signing_input = format!("{header_b64}.{claims_b64}");

        // Sign with ES256
        let signature: Signature = self
            .signing_key
            .expose_secret()
            .signing_key()
            .sign(signing_input.as_bytes());

        let sig_b64 = base64_url_encode(&signature.to_bytes());

        format!("{signing_input}.{sig_b64}")
    }
}

/// `DPoP` proof JWT claims per RFC 9449.
///
/// # Security (SEC-008)
///
/// The `Debug` implementation redacts potentially sensitive fields (`sub`,
/// `ath`) to prevent accidental exposure in logs.
#[derive(Clone, Serialize, Deserialize)]
pub struct DPoPProof {
    /// Unique JWT identifier
    pub jti: String,

    /// HTTP method
    pub htm: String,

    /// HTTP URI (scheme + host + path)
    pub htu: String,

    /// Issued at timestamp (Unix seconds)
    pub iat: i64,

    /// Expiration timestamp (Unix seconds) - SEC-006
    ///
    /// `DPoP` proofs have a maximum lifetime of 300 seconds to limit
    /// replay attack windows per RFC 9449 recommendations.
    pub exp: i64,

    /// Subject identifier (typically `client_id` for IDP validation)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sub: Option<String>,

    /// Access token hash (optional, for token binding)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ath: Option<String>,
}

/// SEC-008: Custom Debug implementation that redacts potentially sensitive
/// fields.
impl std::fmt::Debug for DPoPProof {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DPoPProof")
            .field("jti", &self.jti)
            .field("htm", &self.htm)
            .field("htu", &self.htu)
            .field("iat", &self.iat)
            .field("exp", &self.exp)
            .field("sub", &self.sub.as_ref().map(|_| "[REDACTED]"))
            .field("ath", &self.ath.as_ref().map(|_| "[HASH]"))
            .finish()
    }
}

/// Base64 URL-safe encoding without padding.
fn base64_url_encode(data: &[u8]) -> String {
    use base64::Engine;
    use base64::engine::general_purpose::URL_SAFE_NO_PAD;
    URL_SAFE_NO_PAD.encode(data)
}

/// Get current Unix timestamp in seconds (platform-specific).
///
/// - Native: Uses `std::time::SystemTime`
/// - WASM: Uses `js_sys::Date::now()`
#[cfg(not(target_arch = "wasm32"))]
fn current_timestamp_secs() -> Result<u64, LastIDError> {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs())
        .map_err(|e| LastIDError::crypto(format!("System time error: {e}")))
}

/// Get current Unix timestamp in seconds (WASM version).
#[cfg(target_arch = "wasm32")]
fn current_timestamp_secs() -> Result<u64, LastIDError> {
    // js_sys::Date::now() returns milliseconds as f64
    let millis = js_sys::Date::now();
    // Convert to seconds, safe cast since we're dividing by 1000
    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
    let secs = (millis / 1000.0) as u64;
    Ok(secs)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_generate_keypair() {
        let keypair = DPoPKeyPair::generate().unwrap();
        assert!(!keypair.thumbprint().is_empty());
    }

    #[test]
    fn test_create_dpop_proof() {
        let keypair = DPoPKeyPair::generate().unwrap();
        let proof = keypair
            .create_dpop_proof(
                "POST",
                "https://api.example.com/token",
                None,
                Some("test-client"),
            )
            .unwrap();

        // Verify JWT structure (3 parts separated by dots)
        let parts: Vec<&str> = proof.split('.').collect();
        assert_eq!(parts.len(), 3);
    }

    #[test]
    fn test_create_dpop_proof_with_access_token() {
        let keypair = DPoPKeyPair::generate().unwrap();
        let proof = keypair
            .create_dpop_proof(
                "GET",
                "https://api.example.com/resource",
                Some("test-token"),
                Some("test-client"),
            )
            .unwrap();

        assert!(!proof.is_empty());
    }

    #[test]
    fn test_public_jwk() {
        let keypair = DPoPKeyPair::generate().unwrap();
        let jwk = keypair.public_jwk();

        assert_eq!(jwk["kty"], "EC");
        assert_eq!(jwk["crv"], "P-256");
        assert!(jwk["x"].is_string());
        assert!(jwk["y"].is_string());
    }

    #[test]
    fn test_thumbprint_deterministic() {
        let keypair = DPoPKeyPair::generate().unwrap();
        let thumbprint1 = keypair.thumbprint();
        let thumbprint2 = keypair.thumbprint();
        assert_eq!(thumbprint1, thumbprint2);
    }

    #[test]
    fn test_dpop_proof_has_exp_claim() {
        use base64::Engine;

        // SEC-006: Verify DPoP proofs include expiration claim
        let keypair = DPoPKeyPair::generate().unwrap();
        let proof = keypair
            .create_dpop_proof("POST", "https://api.example.com/token", None, None)
            .unwrap();

        // Decode the claims part (second segment)
        let parts: Vec<&str> = proof.split('.').collect();
        let claims_b64 = parts[1];
        let claims_json = base64::engine::general_purpose::URL_SAFE_NO_PAD
            .decode(claims_b64)
            .unwrap();
        let claims: serde_json::Value = serde_json::from_slice(&claims_json).unwrap();

        // Verify exp claim exists and is in the future
        assert!(
            claims.get("exp").is_some(),
            "DPoP proof must have exp claim"
        );
        let exp = claims["exp"].as_i64().unwrap();
        let iat = claims["iat"].as_i64().unwrap();

        // exp should be iat + 300 seconds
        assert_eq!(exp - iat, 300, "DPoP proof exp should be 300s after iat");
    }

    #[test]
    fn test_no_default_signing_key_wrapper() {
        // SEC-002: SigningKeyWrapper should not implement Default
        // This test verifies at compile time that Default is not implemented.
        // If Default were implemented, this would compile successfully but would be a
        // security issue. The test succeeds by simply compiling - we can't test
        // absence of a trait at runtime.

        // Generate key properly with OsRng
        let keypair = DPoPKeyPair::generate().unwrap();
        assert!(!keypair.thumbprint().is_empty());
    }

    // T093: Keypair serialization roundtrip tests (requires keypair-serialization
    // feature)

    #[cfg(feature = "keypair-serialization")]
    #[test]
    fn test_keypair_serialize_deserialize_roundtrip() {
        // Generate a keypair
        let original = DPoPKeyPair::generate().unwrap();
        let original_thumbprint = original.thumbprint().to_string();

        // Serialize to JSON
        let serialized = serde_json::to_string(&original).unwrap();

        // Verify the serialized form contains PEM markers
        assert!(
            serialized.contains("BEGIN PRIVATE KEY"),
            "Serialized form should contain PEM-encoded private key"
        );

        // Deserialize
        let restored: DPoPKeyPair = serde_json::from_str(&serialized).unwrap();

        // Verify thumbprints match
        assert_eq!(
            original_thumbprint,
            restored.thumbprint(),
            "Thumbprint should be identical after roundtrip"
        );

        // Verify public JWK matches
        assert_eq!(
            original.public_jwk(),
            restored.public_jwk(),
            "Public JWK should be identical after roundtrip"
        );
    }

    #[cfg(feature = "keypair-serialization")]
    #[test]
    fn test_keypair_deserialized_can_create_proofs() {
        // Generate and serialize
        let original = DPoPKeyPair::generate().unwrap();
        let serialized = serde_json::to_string(&original).unwrap();

        // Deserialize
        let restored: DPoPKeyPair = serde_json::from_str(&serialized).unwrap();

        // Create a DPoP proof with the restored keypair
        let proof = restored
            .create_dpop_proof(
                "POST",
                "https://api.example.com/token",
                None,
                Some("test-client"),
            )
            .unwrap();

        // Verify JWT structure (3 parts separated by dots)
        let parts: Vec<&str> = proof.split('.').collect();
        assert_eq!(parts.len(), 3, "Proof should be a valid JWT with 3 parts");

        // Decode and verify the header contains the correct public key
        use base64::Engine;
        let header_json = base64::engine::general_purpose::URL_SAFE_NO_PAD
            .decode(parts[0])
            .unwrap();
        let header: serde_json::Value = serde_json::from_slice(&header_json).unwrap();

        // The JWK in the header should match the keypair's public JWK
        assert_eq!(
            header["jwk"],
            restored.public_jwk(),
            "Proof header should contain the correct public JWK"
        );
    }

    #[cfg(feature = "keypair-serialization")]
    #[test]
    fn test_keypair_serialize_deserialize_pretty_json() {
        // Test with pretty-printed JSON (common for config files)
        let original = DPoPKeyPair::generate().unwrap();
        let serialized = serde_json::to_string_pretty(&original).unwrap();

        let restored: DPoPKeyPair = serde_json::from_str(&serialized).unwrap();
        assert_eq!(original.thumbprint(), restored.thumbprint());
    }

    #[cfg(feature = "keypair-serialization")]
    #[test]
    fn test_keypair_deserialize_invalid_pem() {
        // Test that invalid PEM data fails gracefully
        let invalid_json = r#"{"private_key_pem":"not-a-valid-pem"}"#;
        let result: Result<DPoPKeyPair, _> = serde_json::from_str(invalid_json);

        assert!(result.is_err(), "Invalid PEM should fail to deserialize");
    }

    #[cfg(feature = "keypair-serialization")]
    #[test]
    fn test_keypair_serialization_format() {
        // Verify the serialization format matches the expected structure
        let keypair = DPoPKeyPair::generate().unwrap();
        let serialized = serde_json::to_string(&keypair).unwrap();

        // Parse as generic JSON to inspect structure
        let json: serde_json::Value = serde_json::from_str(&serialized).unwrap();

        // Should have exactly one field: private_key_pem
        assert!(json.is_object());
        let obj = json.as_object().unwrap();
        assert_eq!(
            obj.len(),
            1,
            "Serialized form should have exactly one field"
        );
        assert!(
            obj.contains_key("private_key_pem"),
            "Serialized form should contain 'private_key_pem' field"
        );

        // The PEM should be a valid P-256 private key
        let pem = obj["private_key_pem"].as_str().unwrap();
        assert!(pem.contains("-----BEGIN PRIVATE KEY-----"));
        assert!(pem.contains("-----END PRIVATE KEY-----"));
    }

    #[cfg(feature = "keypair-serialization")]
    #[test]
    fn test_multiple_keypairs_have_different_serializations() {
        // Ensure each generated keypair serializes to a unique value
        let kp1 = DPoPKeyPair::generate().unwrap();
        let kp2 = DPoPKeyPair::generate().unwrap();

        let s1 = serde_json::to_string(&kp1).unwrap();
        let s2 = serde_json::to_string(&kp2).unwrap();

        assert_ne!(
            s1, s2,
            "Different keypairs should have different serializations"
        );
        assert_ne!(
            kp1.thumbprint(),
            kp2.thumbprint(),
            "Different keypairs should have different thumbprints"
        );
    }

    // =========================================================================
    // SEC-001: Zeroization Tests
    // =========================================================================

    #[test]
    fn test_signing_key_wrapper_zeroize_zeros_bytes() {
        // SEC-001: Verify that zeroize() actually zeros the scalar bytes
        use rand::rngs::OsRng;

        let key = SigningKey::random(&mut OsRng);
        let original_bytes = key.to_bytes();

        // Verify original bytes are not all zeros (extremely unlikely for random key)
        assert!(
            original_bytes.iter().any(|&b| b != 0),
            "Random key should not be all zeros"
        );

        // Create wrapper and zeroize it
        let mut wrapper = SigningKeyWrapper::new(key);

        // Verify the scalar_bytes match the original key
        assert_eq!(
            &*wrapper.scalar_bytes, &*original_bytes,
            "Wrapper should store correct scalar bytes"
        );

        // Zeroize the wrapper
        wrapper.zeroize();

        // Verify scalar_bytes are now all zeros
        assert!(
            wrapper.scalar_bytes.iter().all(|&b| b == 0),
            "After zeroize(), scalar_bytes must be all zeros"
        );
    }

    #[test]
    fn test_signing_key_wrapper_drop_calls_zeroize() {
        // SEC-001: Verify that Drop calls zeroize
        // This is a structural test - we verify the Drop impl exists and calls zeroize
        // We can't directly verify memory after drop, but we can test the wrapper
        // behavior
        use rand::rngs::OsRng;

        let key = SigningKey::random(&mut OsRng);
        let wrapper = SigningKeyWrapper::new(key);

        // Clone the wrapper to verify it works
        let cloned = wrapper.clone();

        // Verify both wrappers can sign (proving the key is valid)
        let signing_key = cloned.signing_key();
        let _ = signing_key.verifying_key(); // Just verify we can access the key

        // When wrapper goes out of scope here, Drop should be called
        // which should call zeroize()
        drop(wrapper);

        // The cloned wrapper should still work
        let _ = cloned.signing_key().verifying_key();
    }

    #[test]
    fn test_signing_key_wrapper_clone_creates_independent_copy() {
        // SEC-001: Verify that cloned wrappers have independent scalar_bytes
        use rand::rngs::OsRng;

        let key = SigningKey::random(&mut OsRng);
        let mut wrapper1 = SigningKeyWrapper::new(key);
        let wrapper2 = wrapper1.clone();

        // Zeroize wrapper1
        wrapper1.zeroize();

        // wrapper1's bytes should be zeroed
        assert!(
            wrapper1.scalar_bytes.iter().all(|&b| b == 0),
            "wrapper1 should be zeroed"
        );

        // wrapper2's bytes should NOT be zeroed (independent copy)
        assert!(
            wrapper2.scalar_bytes.iter().any(|&b| b != 0),
            "wrapper2 should retain original key (not zeroed)"
        );
    }

    #[test]
    fn test_default_zeroize_strategy() {
        // SEC-001: Test the DefaultZeroizeStrategy implementation
        let strategy = DefaultZeroizeStrategy;

        let mut bytes = [0xFFu8; P256_SCALAR_SIZE]; // All 0xFF bytes
        strategy.zeroize_key_bytes(&mut bytes);

        assert!(
            bytes.iter().all(|&b| b == 0),
            "DefaultZeroizeStrategy should zero all bytes"
        );
    }

    #[test]
    fn test_custom_zeroize_strategy_can_be_implemented() {
        // SEC-001: Verify that custom strategies can be implemented
        struct TestStrategy {
            pattern: u8,
        }

        impl ZeroizeStrategy for TestStrategy {
            fn zeroize_key_bytes(&self, bytes: &mut [u8; P256_SCALAR_SIZE]) {
                // Custom strategy: fill with pattern then zero
                bytes.fill(self.pattern);
                bytes.zeroize();
            }
        }

        let strategy = TestStrategy { pattern: 0xAA };
        let mut bytes = [0xFFu8; P256_SCALAR_SIZE];
        strategy.zeroize_key_bytes(&mut bytes);

        assert!(
            bytes.iter().all(|&b| b == 0),
            "Custom strategy should ultimately zero bytes"
        );
    }

    #[test]
    fn test_zeroize_strategy_is_send_sync() {
        // SEC-001: Verify ZeroizeStrategy is Send + Sync for thread safety
        fn assert_send_sync<T: Send + Sync>() {}
        assert_send_sync::<DefaultZeroizeStrategy>();
    }

    #[test]
    fn test_dpop_keypair_zeroizes_on_drop() {
        // SEC-001: High-level test verifying DPoPKeyPair cleanup
        // We can't directly test memory after drop, but we can verify:
        // 1. The keypair works before drop
        // 2. No panic occurs during drop

        let keypair = DPoPKeyPair::generate().unwrap();
        let thumbprint = keypair.thumbprint().to_string();

        // Verify keypair works
        let proof = keypair
            .create_dpop_proof("GET", "https://example.com", None, None)
            .unwrap();
        assert!(!proof.is_empty());
        assert!(!thumbprint.is_empty());

        // Explicitly drop - this should trigger zeroization without panic
        drop(keypair);

        // If we got here, drop completed successfully
    }

    #[test]
    fn test_zeroizing_wrapper_zeros_array() {
        // SEC-001: Verify Zeroizing<[u8; 32]> works correctly
        let mut zeroizing = Zeroizing::new([0xFFu8; 32]);

        // Verify initial state
        assert!(zeroizing.iter().all(|&b| b == 0xFF));

        // Zeroize
        zeroizing.zeroize();

        // Verify zeroed
        assert!(
            zeroizing.iter().all(|&b| b == 0),
            "Zeroizing wrapper should zero all bytes"
        );
    }

    #[test]
    fn test_scalar_bytes_size_matches_p256() {
        // SEC-001: Verify our constant matches actual P-256 scalar size
        use rand::rngs::OsRng;

        let key = SigningKey::random(&mut OsRng);
        let bytes = key.to_bytes();

        assert_eq!(
            bytes.len(),
            P256_SCALAR_SIZE,
            "P256_SCALAR_SIZE constant should match actual key size"
        );
    }
}
