use base64::{
    Engine,
    engine::general_purpose::{STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD},
};
use candid::{CandidType, Principal};
use http::header::{AUTHORIZATION, HeaderMap, HeaderName};
use ic_auth_types::{
    ByteBufB64, DelegationCompact, SignedDelegation, SignedDelegationCompact, cbor_from_slice,
    deterministic_cbor_into_vec,
};
use ic_canister_sig_creation::delegation_signature_msg;
use serde::{Deserialize, Serialize};

#[cfg(feature = "identity")]
use ic_agent::{Identity, Signature};

use ic_canister_sig_creation::IC_ROOT_PK_DER;

#[cfg(feature = "identity")]
use crate::sha3_256;
use crate::{Algorithm, user_public_key_from_der, verify_basic_sig, verify_canister_sig};

// pub use ic_signature_verification::verify_canister_sig;

/// The Internet Computer's anonymous principal identifier.
/// This is used when no authenticated identity is provided.
pub const ANONYMOUS_PRINCIPAL: Principal = Principal::anonymous();

/// Maximum allowed time drift in milliseconds for delegation verification.
/// This prevents replay attacks while allowing for reasonable clock differences.
pub const PERMITTED_DRIFT_MS: u64 = 300 * 1000;

/// Maximum allowed delegation chain length.
pub const MAX_DELEGATION_CHAIN_LENGTH: usize = 5;

/// Domain separator for delegation signature messages.
pub const IC_REQUEST_AUTH_DELEGATION_DOMAIN_SEPARATOR: &[u8] = b"\x1Aic-request-auth-delegation";

/// HTTP header for the caller's public key used in authentication.
/// This header contains the base64url-encoded public key of the caller.
pub static HEADER_IC_AUTH_PUBKEY: HeaderName = HeaderName::from_static("ic-auth-pubkey");

/// HTTP header for the request content hash.
/// This header contains a base64url-encoded hash of the request content,
/// which can be customized based on business logic requirements.
pub static HEADER_IC_AUTH_CONTENT_DIGEST: HeaderName =
    HeaderName::from_static("ic-auth-content-digest");

/// HTTP header for the signature of the content digest.
/// This header contains a base64url-encoded cryptographic signature
/// that proves the authenticity of the content digest.
pub static HEADER_IC_AUTH_SIGNATURE: HeaderName = HeaderName::from_static("ic-auth-signature");

/// HTTP header for the delegation chain used in authentication.
/// This header contains a base64url-encoded CBOR representation of the delegation chain
/// that connects the public key to the signature.
pub static HEADER_IC_AUTH_DELEGATION: HeaderName = HeaderName::from_static("ic-auth-delegation");

/// HTTP header for the authenticated user principal.
/// This header contains the textual representation of the authenticated user's principal ID,
/// or the anonymous principal if authentication fails or is not provided.
pub static HEADER_IC_AUTH_USER: HeaderName = HeaderName::from_static("ic-auth-user");

/// Verifies a signature using the public key and the mainnet IC root public key.
///
/// # Arguments
/// * `public_key` - The DER-encoded public key to verify against
/// * `msg` - The message that was signed
/// * `signature` - The signature to verify
/// * `current_time_ns` - The current time in nanoseconds since the Unix epoch, used for validating canister signatures with time-based constraints
///
/// # Returns
/// * `Ok(())` if the signature is valid
/// * `Err(String)` with an error message if verification fails
pub fn verify_sig(
    public_key: &[u8],
    msg: &[u8],
    signature: &[u8],
    current_time_ns: &u128,
) -> Result<(), String> {
    verify_sig_with_rootkey(IC_ROOT_PK_DER, public_key, msg, signature, current_time_ns)
}

/// Verifies a signature using the public key and a specified IC root public key.
/// This function allows verification against different IC networks by providing
/// a custom root public key.
///
/// # Arguments
/// * `ic_root_public_key_raw` - The raw IC root public key to use for verification
/// * `public_key` - The DER-encoded public key to verify against
/// * `msg` - The message that was signed
/// * `signature` - The signature to verify
/// * `current_time_ns` - The current time in nanoseconds since the Unix epoch, used for validating canister signatures with time-based constraints
///
/// # Returns
/// * `Ok(())` if the signature is valid
/// * `Err(String)` with an error message if verification fails
pub fn verify_sig_with_rootkey(
    ic_root_public_key_raw: &[u8],
    public_key: &[u8],
    msg: &[u8],
    signature: &[u8],
    current_time_ns: &u128,
) -> Result<(), String> {
    let (alg, pk) = user_public_key_from_der(public_key)?;
    match alg {
        Algorithm::IcCanisterSignature => verify_canister_sig(
            msg,
            signature,
            public_key,
            ic_root_public_key_raw,
            current_time_ns,
            None,
        ),
        _ => verify_basic_sig(alg, &pk, msg, signature),
    }
}

/// The authentication envelope for Internet Computer authentication.
///
/// This structure encapsulates all the necessary components for authenticating
/// a request to an Internet Computer service. It includes the public key of the
/// sender, the signature, the content digest, and an optional delegation chain.
///
/// The envelope can be serialized to and from various formats, including bytes,
/// base64 strings, and HTTP headers, making it versatile for different transport
/// mechanisms.
#[derive(Clone, Debug, CandidType, Deserialize, Serialize)]
pub struct SignedEnvelope {
    /// The public key of the self-authenticating principal this request is from.
    /// This is the head of the delegation chain (if any) and is used to derive
    /// the principal ID of the sender.
    #[serde(rename = "p", alias = "pubkey", alias = "public_key")]
    pub pubkey: ByteBufB64,

    /// A cryptographic signature authorizing the request.
    /// When delegations are involved, this is the signature from the tail of the
    /// delegation chain, not necessarily made by the owner of `pubkey`.
    #[serde(rename = "s", alias = "signature")]
    pub signature: ByteBufB64,

    /// The request content's hash digest that was signed by the sender.
    /// This is typically a SHA-256 or SHA3-256 hash of the request content.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[serde(rename = "h", alias = "digest")]
    pub digest: Option<ByteBufB64>,

    /// The chain of delegations connecting `pubkey` to `signature`, in order.
    /// Each delegation authorizes the next entity in the chain to sign on behalf
    /// of the previous entity, forming a chain of trust from the original identity
    /// to the actual signer.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[serde(rename = "d", alias = "delegation")]
    pub delegation: Option<Vec<SignedDelegationCompact>>,
}

#[cfg(feature = "identity")]
impl TryFrom<Signature> for SignedEnvelope {
    type Error = String;
    fn try_from(sig: Signature) -> Result<Self, Self::Error> {
        Ok(Self {
            pubkey: sig
                .public_key
                .ok_or_else(|| "missing public_key".to_string())?
                .into(),
            signature: sig
                .signature
                .ok_or_else(|| "missing signature".to_string())?
                .into(),
            digest: None,
            delegation: sig.delegations.map(|delegations| {
                delegations
                    .into_iter()
                    .map(|d| SignedDelegationCompact {
                        delegation: DelegationCompact {
                            pubkey: d.delegation.pubkey.into(),
                            expiration: d.delegation.expiration,
                            targets: d.delegation.targets,
                            permissions: d.delegation.permissions.map(|p| match p {
                                ic_agent::identity::DelegationPermissions::Queries => {
                                    ic_auth_types::DelegationPermissions::Queries
                                }
                                ic_agent::identity::DelegationPermissions::All => {
                                    ic_auth_types::DelegationPermissions::All
                                }
                            }),
                        },
                        signature: d.signature.into(),
                    })
                    .collect::<Vec<_>>()
            }),
        })
    }
}

impl SignedEnvelope {
    /// Returns the sender's principal ID derived from the public key.
    ///
    /// This computes a self-authenticating principal ID based on the public key
    /// in the envelope, which uniquely identifies the sender.
    ///
    /// # Returns
    /// * `Principal` - The principal ID of the sender
    pub fn sender(&self) -> Principal {
        Principal::self_authenticating(&self.pubkey)
    }

    /// Encodes the SignedEnvelope into a binary representation.
    ///
    /// # Returns
    /// * `Vec<u8>` - The CBOR-encoded binary representation of the envelope
    pub fn to_bytes(&self) -> Vec<u8> {
        deterministic_cbor_into_vec(&self).expect("failed to encode SignedEnvelope")
    }

    /// Decodes a SignedEnvelope from its binary representation.
    ///
    /// # Arguments
    /// * `bytes` - The CBOR-encoded binary representation of the envelope
    ///
    /// # Returns
    /// * `Result<Self, String>` - The decoded envelope or an error message
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
        cbor_from_slice(bytes).map_err(|err| format!("failed to decode SignedEnvelope data: {err}"))
    }

    /// Encodes the SignedEnvelope into a base64url string.
    ///
    /// # Returns
    /// * `String` - The base64url-encoded representation of the envelope
    pub fn to_base64(&self) -> String {
        URL_SAFE.encode(self.to_bytes())
    }

    /// Decodes a SignedEnvelope from a base64url string.
    ///
    /// # Arguments
    /// * `s` - The base64url-encoded string representation of the envelope
    ///
    /// # Returns
    /// * `Result<Self, String>` - The decoded envelope or an error message
    pub fn from_base64(s: &str) -> Result<Self, String> {
        decode_base64(s).and_then(|data| Self::from_bytes(&data))
    }

    /// Creates a SignedEnvelope by signing a message with the provided identity.
    ///
    /// This computes the SHA3-256 hash of the message and signs it with the identity.
    ///
    /// # Arguments
    /// * `identity` - The identity to sign with, implementing the `Identity` trait
    /// * `message` - The message to sign
    ///
    /// # Returns
    /// * `Result<Self, String>` - The signed envelope or an error message
    #[cfg(feature = "identity")]
    pub fn sign_message(identity: &impl Identity, message: &[u8]) -> Result<Self, String> {
        Self::sign_digest(identity, sha3_256(message).into())
    }

    /// Creates a SignedEnvelope by signing a pre-computed digest with the provided identity.
    ///
    /// # Arguments
    /// * `identity` - The identity to sign with, implementing the `Identity` trait
    /// * `digest` - The pre-computed digest to sign
    ///
    /// # Returns
    /// * `Result<Self, String>` - The signed envelope or an error message
    #[cfg(feature = "identity")]
    pub fn sign_digest(identity: &impl Identity, digest: Vec<u8>) -> Result<Self, String> {
        let sig = identity
            .sign_arbitrary(&digest)
            .map_err(|err| format!("{err:?}"))?;
        let mut envelope: Self = sig.try_into()?;
        envelope.digest = Some(digest.into());
        Ok(envelope)
    }

    /// Verifies the SignedEnvelope according to the Internet Computer authentication rules.
    ///
    /// Verification rules:
    /// - Delegation chain length must not exceed 5
    /// - Delegations must not be expired (considering the permitted time drift)
    /// - Each signature in the chain must verify against the corresponding public key
    /// - If delegation targets are specified, the expected target must be included
    /// - The content digest must match the expected digest (if provided)
    ///
    /// # Arguments
    /// * `now_ms` - The current time in milliseconds since the Unix epoch
    /// * `expect_target` - Optional canister ID that should be in the delegation targets
    /// * `expect_digest` - Optional expected content digest to verify against
    ///
    /// # Returns
    /// * `Ok(())` if verification succeeds
    /// * `Err(String)` with a detailed error message if verification fails
    pub fn verify(
        &self,
        now_ms: u64,
        expect_target: Option<Principal>,
        expect_digest: Option<&[u8]>,
    ) -> Result<(), String> {
        let current_time_ns = now_ms as u128 * 1_000_000;
        let digest = match (self.digest.as_ref(), expect_digest) {
            (Some(digest), Some(expect_digest)) => {
                if digest.as_slice() != expect_digest {
                    return Err("Content digest does not match".to_string());
                }
                digest
            }
            (Some(digest), None) => digest.as_slice(),
            (None, Some(expect_digest)) => expect_digest,
            (None, None) => {
                return Err("No content digest provided for verification".to_string());
            }
        };

        let mut last_verified = &self.pubkey;
        if let Some(delegation) = &self.delegation {
            if delegation.len() > MAX_DELEGATION_CHAIN_LENGTH {
                return Err(format!(
                    "Delegation chain length exceeds the limit {}: {}",
                    MAX_DELEGATION_CHAIN_LENGTH,
                    delegation.len()
                ));
            }

            for d in delegation {
                check_delegation_expiration(d.delegation.expiration, now_ms)?;

                if let (Some(targets), Some(target)) = (&d.delegation.targets, &expect_target) {
                    // Should check if the expected target is in the delegation targets
                    if !targets.contains(target) {
                        return Err(format!(
                            "Expected target canister ID '{expect_target:?}' is not in the delegation targets: {targets:?}"
                        ));
                    }
                }

                let message = delegation_signed_message(&d.delegation);
                verify_sig(last_verified, &message, &d.signature, &current_time_ns)?;

                last_verified = &d.delegation.pubkey;
            }
        }

        verify_sig(last_verified, digest, &self.signature, &current_time_ns)
    }

    /// Extracts a SignedEnvelope from the Authorization header.
    ///
    /// This looks for an "ICP" authentication scheme in the Authorization header
    /// and decodes the associated token as a SignedEnvelope.
    ///
    /// # Arguments
    /// * `headers` - The HTTP headers to extract from
    ///
    /// # Returns
    /// * `Option<Self>` - The extracted envelope, or None if not found or invalid
    pub fn from_authorization(headers: &HeaderMap) -> Option<Self> {
        if let Some(token) = headers.get(AUTHORIZATION)
            && let Ok(token) = token.to_str()
            && let Some(token) = token.strip_prefix("ICP ")
            && let Ok(envelope) = Self::from_base64(token)
        {
            return Some(envelope);
        }
        None
    }

    /// Adds the SignedEnvelope to the Authorization header.
    ///
    /// This encodes the envelope as a base64url string and adds it to the
    /// Authorization header with the "ICP" authentication scheme.
    ///
    /// # Arguments
    /// * `headers` - The HTTP headers to add to
    ///
    /// # Returns
    /// * `Result<(), String>` - Success or an error message if adding fails
    pub fn to_authorization(&self, headers: &mut HeaderMap) -> Result<(), String> {
        headers.insert(
            AUTHORIZATION,
            format!("ICP {}", self.to_base64())
                .parse()
                .map_err(|err| format!("insert {AUTHORIZATION} header failed: {err}"))?,
        );
        Ok(())
    }

    /// Extracts a SignedEnvelope from the IC-Auth-* HTTP headers.
    ///
    /// This looks for the individual components of the envelope in separate headers
    /// and reconstructs the envelope from them.
    ///
    /// # Arguments
    /// * `headers` - The HTTP headers to extract from
    ///
    /// # Returns
    /// * `Option<Self>` - The extracted envelope, or None if not found or invalid.
    ///   A delegation header that is present but malformed invalidates the whole envelope.
    pub fn from_headers(headers: &HeaderMap) -> Option<Self> {
        let pubkey = extract_data(headers, &HEADER_IC_AUTH_PUBKEY)?;
        let digest = extract_data(headers, &HEADER_IC_AUTH_CONTENT_DIGEST)?;
        let signature = extract_data(headers, &HEADER_IC_AUTH_SIGNATURE)?;
        let delegation = match headers.get(&HEADER_IC_AUTH_DELEGATION) {
            Some(_) => {
                let data = extract_data(headers, &HEADER_IC_AUTH_DELEGATION)?;
                Some(cbor_from_slice(&data[..]).ok()?)
            }
            None => None,
        };

        Some(Self {
            pubkey: pubkey.into(),
            signature: signature.into(),
            digest: Some(digest.into()),
            delegation,
        })
    }

    /// Adds the SignedEnvelope components to the IC-Auth-* HTTP headers.
    ///
    /// This breaks down the envelope into its components and adds each one
    /// to a separate HTTP header.
    ///
    /// # Arguments
    /// * `headers` - The HTTP headers to add to
    ///
    /// # Returns
    /// * `Result<(), String>` - Success or an error message
    pub fn to_headers(&self, headers: &mut HeaderMap) -> Result<(), String> {
        headers.insert(
            &HEADER_IC_AUTH_PUBKEY,
            URL_SAFE_NO_PAD
                .encode(&self.pubkey)
                .parse()
                .map_err(|err| format!("insert {HEADER_IC_AUTH_PUBKEY} header failed: {err}"))?,
        );
        if let Some(digest) = &self.digest {
            headers.insert(
                &HEADER_IC_AUTH_CONTENT_DIGEST,
                URL_SAFE_NO_PAD.encode(digest).parse().map_err(|err| {
                    format!("insert {HEADER_IC_AUTH_CONTENT_DIGEST} header failed: {err}")
                })?,
            );
        }
        headers.insert(
            &HEADER_IC_AUTH_SIGNATURE,
            URL_SAFE_NO_PAD
                .encode(&self.signature)
                .parse()
                .map_err(|err| format!("insert {HEADER_IC_AUTH_SIGNATURE} header failed: {err}"))?,
        );
        if let Some(delegations) = &self.delegation {
            headers.insert(
                &HEADER_IC_AUTH_DELEGATION,
                URL_SAFE_NO_PAD
                    .encode(deterministic_cbor_into_vec(&delegations)?)
                    .parse()
                    .map_err(|err| {
                        format!("insert {HEADER_IC_AUTH_DELEGATION} header failed: {err}")
                    })?,
            );
        }
        Ok(())
    }
}

/// Verifies a delegation chain.
///
/// This function checks the validity of a chain of signed delegations,
/// ensuring that each delegation is not expired, and that the signatures
/// are valid according to the provided IC root public key.
///
/// # Arguments
/// * `user_pubkey` - The der public key of the user to sign the delegation chain
/// * `session_pubkey` - The public key of the session to verify against
/// * `delegations` - The chain of signed delegations to verify
/// * `now_ms` - The current time in milliseconds since the Unix epoch
/// * `ic_root_public_key_raw` - Optional raw IC root public key for signature verification
pub fn verify_delegation_chain(
    user_pubkey: &[u8],
    session_pubkey: &[u8],
    delegations: &[SignedDelegationCompact],
    now_ms: u64,
    ic_root_public_key_raw: Option<&[u8]>,
) -> Result<(), String> {
    if delegations.is_empty() {
        return Err("Delegation chain is empty".to_string());
    }

    if delegations.len() > MAX_DELEGATION_CHAIN_LENGTH {
        return Err(format!(
            "Delegation chain length exceeds the limit {}: {}",
            MAX_DELEGATION_CHAIN_LENGTH,
            delegations.len()
        ));
    }

    let current_time_ns = now_ms as u128 * 1_000_000;
    let ic_root_public_key_raw = ic_root_public_key_raw.unwrap_or(IC_ROOT_PK_DER);
    let mut last_verified = user_pubkey;
    for d in delegations {
        check_delegation_expiration(d.delegation.expiration, now_ms)?;

        let message = delegation_signed_message(&d.delegation);
        verify_sig_with_rootkey(
            ic_root_public_key_raw,
            last_verified,
            &message,
            &d.signature,
            &current_time_ns,
        )?;

        last_verified = &d.delegation.pubkey;
    }
    if last_verified != session_pubkey {
        return Err(format!(
            "Last verified public key does not match session public key:\n\
             Last verified: {}\n\
             Session public key: {}",
            hex::encode(last_verified),
            hex::encode(session_pubkey)
        ));
    }

    Ok(())
}

fn is_delegation_expired(expiration_ns: u64, now_ms: u64) -> bool {
    let earliest_valid_ms = now_ms.saturating_sub(PERMITTED_DRIFT_MS);
    expiration_ns / 1_000_000 < earliest_valid_ms
}

fn check_delegation_expiration(expiration_ns: u64, now_ms: u64) -> Result<(), String> {
    if is_delegation_expired(expiration_ns, now_ms) {
        return Err(format!(
            "Delegation has expired:\n\
             Provided expiry:    {}\n\
             Local replica timestamp: {}",
            expiration_ns,
            now_ms as u128 * 1_000_000,
        ));
    }
    Ok(())
}

/// Builds the domain-separated message whose signature authorizes the delegation.
fn delegation_signed_message(delegation: &DelegationCompact) -> Vec<u8> {
    let targets = delegation.targets.as_ref().map(|targets| {
        targets
            .iter()
            .map(|p| p.as_slice().to_vec())
            .collect::<Vec<Vec<u8>>>()
    });
    let msg = delegation_signature_msg(
        delegation.pubkey.as_slice(),
        delegation.expiration,
        targets.as_ref(),
    );
    let mut message =
        Vec::with_capacity(IC_REQUEST_AUTH_DELEGATION_DOMAIN_SEPARATOR.len() + msg.len());
    message.extend_from_slice(IC_REQUEST_AUTH_DELEGATION_DOMAIN_SEPARATOR);
    message.extend(msg);
    message
}

/// Extracts base64url-encoded data from an HTTP header.
///
/// # Arguments
/// * `headers` - The HTTP headers to extract from
/// * `key` - The name of the header to extract
///
/// # Returns
/// * `Option<Vec<u8>>` - The decoded data, or None if not found or invalid
pub fn extract_data(headers: &HeaderMap, key: &HeaderName) -> Option<Vec<u8>> {
    if let Some(val) = headers.get(key)
        && let Ok(val) = val.to_str()
        && let Ok(data) = decode_base64(val)
    {
        return Some(data);
    }
    None
}

/// Extracts the authenticated user principal from the HTTP headers.
///
/// This looks for the IC-Auth-User header and parses it as a Principal.
/// If the header is not found or invalid, it returns the anonymous principal.
///
/// # Arguments
/// * `headers` - The HTTP headers to extract from
///
/// # Returns
/// * `Principal` - The authenticated user principal or anonymous principal
pub fn extract_user(headers: &HeaderMap) -> Principal {
    if let Some(caller) = headers.get(&HEADER_IC_AUTH_USER) {
        if let Ok(caller) = Principal::from_text(caller.to_str().unwrap_or_default()) {
            caller
        } else {
            ANONYMOUS_PRINCIPAL
        }
    } else {
        ANONYMOUS_PRINCIPAL
    }
}

/// Decodes base64-encoded data.
///
/// This function handles both padded and unpadded data, in either the
/// base64url or the standard base64 alphabet.
///
/// # Arguments
/// * `data` - The base64-encoded string to decode
///
/// # Returns
/// * `Result<Vec<u8>, String>` - The decoded data or an error message
pub fn decode_base64(data: &str) -> Result<Vec<u8>, String> {
    let data = data.trim().trim_end_matches('=');
    if data.contains(['+', '/']) {
        STANDARD_NO_PAD.decode(data)
    } else {
        URL_SAFE_NO_PAD.decode(data)
    }
    .map_err(|err| format!("failed to decode base64 data: {err}"))
}

/// Full-name representation of [`SignedEnvelope`].
///
/// `SignedEnvelope` serializes with compact field names (`p`, `s`, `h`, `d`)
/// for transport. This companion type is useful when an API boundary wants
/// descriptive names while retaining aliases for compact payloads.
#[derive(Clone, Debug, CandidType, Deserialize, Serialize)]
pub struct SignedEnvelopeFull {
    /// The DER public key of the user identity.
    #[serde(alias = "p")]
    pub pubkey: ByteBufB64,

    /// The signature over the envelope digest.
    #[serde(alias = "s")]
    pub signature: ByteBufB64,

    /// Optional digest that was signed by `signature`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[serde(alias = "h")]
    pub digest: Option<ByteBufB64>,

    /// Optional full-name delegation chain.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[serde(alias = "d")]
    pub delegation: Option<Vec<SignedDelegation>>,
}

impl From<SignedEnvelope> for SignedEnvelopeFull {
    fn from(envelope: SignedEnvelope) -> Self {
        Self {
            pubkey: envelope.pubkey,
            signature: envelope.signature,
            digest: envelope.digest,
            delegation: envelope
                .delegation
                .map(|delegations| delegations.into_iter().map(Into::into).collect()),
        }
    }
}

impl From<SignedEnvelopeFull> for SignedEnvelope {
    fn from(envelope: SignedEnvelopeFull) -> Self {
        Self {
            pubkey: envelope.pubkey,
            signature: envelope.signature,
            digest: envelope.digest,
            delegation: envelope
                .delegation
                .map(|delegations| delegations.into_iter().map(Into::into).collect()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[cfg(feature = "identity")]
    use crate::unix_timestamp;
    #[cfg(feature = "identity")]
    use cbor2::Value;
    #[cfg(feature = "identity")]
    use ic_agent::{
        Identity,
        identity::{
            BasicIdentity, DelegatedIdentity, Delegation as AgentDelegation,
            SignedDelegation as AgentSignedDelegation,
        },
    };
    use ic_auth_types::DelegationCompact;
    use ic_canister_sig_creation::CanisterSigPublicKey;
    #[cfg(feature = "identity")]
    use std::time::Duration;

    fn sample_envelope() -> SignedEnvelope {
        SignedEnvelope {
            pubkey: vec![1, 2, 3].into(),
            signature: vec![4, 5, 6].into(),
            digest: Some(vec![7, 8, 9].into()),
            delegation: None,
        }
    }

    #[cfg(feature = "identity")]
    #[test]
    fn test_envelope_with_ed25519() {
        let secret = [8u8; 32];
        let id = BasicIdentity::from_raw_key(&secret);
        println!("id: {:?}", id.sender().unwrap().to_text());
        // jjn6g-sh75l-r3cxb-wxrkl-frqld-6p6qq-d4ato-wske5-op7s5-n566f-bqe

        let msg = b"hello world";
        let mut headers = HeaderMap::new();
        let se = SignedEnvelope::sign_message(&id, msg).unwrap();
        assert_eq!(se.sender(), id.sender().unwrap());
        se.to_headers(&mut headers).unwrap();

        let mut se2 = SignedEnvelope::from_headers(&headers).unwrap();
        assert!(
            se2.verify(unix_timestamp().as_millis() as u64, None, None)
                .is_ok()
        );

        se2.digest = Some(sha3_256(b"hello world 2").to_vec().into());
        assert!(
            se2.verify(unix_timestamp().as_millis() as u64, None, None)
                .is_err()
        );

        let digest = sha3_256(msg);
        let sig = id.sign_arbitrary(digest.as_slice()).unwrap();
        let se: SignedEnvelope = sig.try_into().unwrap();
        assert!(
            se.verify(
                unix_timestamp().as_millis() as u64,
                None,
                Some(digest.as_slice())
            )
            .is_ok()
        );

        let delegated = crate::delegated_basic_identity(&id, 3600 * 1000);
        let se = SignedEnvelope::sign_message(&delegated, msg).unwrap();
        assert_eq!(se.delegation.as_ref().unwrap().len(), 1);
        assert!(
            se.verify(unix_timestamp().as_millis() as u64, None, None)
                .is_ok()
        );

        let sig = delegated.sign_arbitrary(digest.as_slice()).unwrap();
        let se: SignedEnvelope = sig.try_into().unwrap();
        assert_eq!(se.delegation.as_ref().unwrap().len(), 1);
        assert!(
            se.verify(
                unix_timestamp().as_millis() as u64,
                None,
                Some(digest.as_slice())
            )
            .is_ok()
        );
    }

    #[cfg(feature = "identity")]
    #[test]
    fn test_envelope_with_ed25519_2() {
        // test data from ts/ic-auth/src/identity.test.ts
        let secret = [8u8; 32];
        let id = BasicIdentity::from_raw_key(&secret);

        let msg = "pGFk92FoWCDy_PBrUtbrh328ZTWvrZnuiE2EMKHfMz_1M6f3JN1nq2FwWCwwKjAFBgMrZXADIQATmPYsbRpFfFG6aktfPb0vafypMhYhjciZfkFr0X2TymFzWEAzEYt2uq3q2BiMmgz91CLI6Sj0Vs90pE-bTd37h35FpBOonchIBqXyjtBpnfguDbZkKzy_VWbs9bDx29_5lqwD";
        let se = SignedEnvelope::from_base64(msg).unwrap();
        assert_eq!(id.public_key().unwrap(), se.pubkey.as_slice());
        assert_eq!(
            se.digest.as_ref().unwrap().as_slice(),
            hex::decode("f2fcf06b52d6eb877dbc6535afad99ee884d8430a1df333ff533a7f724dd67ab")
                .unwrap()
                .as_slice()
        );

        assert!(
            se.verify(unix_timestamp().as_millis() as u64, None, None)
                .is_ok()
        );

        let msg = "o2Fk92FwWCwwKjAFBgMrZXADIQATmPYsbRpFfFG6aktfPb0vafypMhYhjciZfkFr0X2TymFzWEAzEYt2uq3q2BiMmgz91CLI6Sj0Vs90pE-bTd37h35FpBOonchIBqXyjtBpnfguDbZkKzy_VWbs9bDx29_5lqwD";
        let se = SignedEnvelope::from_base64(msg).unwrap();
        assert_eq!(id.public_key().unwrap(), se.pubkey.as_slice());
        assert_eq!(se.digest, None);

        assert!(
            se.verify(unix_timestamp().as_millis() as u64, None, None)
                .is_err()
        );

        let obj = vec![
            (Value::from("z"), Value::from("z")),     // 0x617a
            (Value::from("aa"), Value::from("aa")),   // 0x626161
            (Value::from(10), Value::from(10)),       // 0x0a
            (Value::from(100), Value::from(100)),     // 0x1864
            (Value::from(-1), Value::from(-1)),       // 0x20
            (Value::from(false), Value::from(false)), // 0xf4
        ];

        let data = deterministic_cbor_into_vec(&Value::Map(obj)).unwrap();
        let digest = sha3_256(&data);
        assert!(
            se.verify(
                unix_timestamp().as_millis() as u64,
                None,
                Some(digest.as_slice())
            )
            .is_ok()
        );

        // generated by ic-certification 2.6
        let msg = "pGFkgaJhZKJhZRsYojcfvOoZmmFwWCwwKjAFBgMrZXADIQBoGlRAsc_ADaKVxzccHBGsazda-ERmveqkjSZFbiY2nmFzWQZu2dn3omtjZXJ0aWZpY2F0ZVkE1dnZ96NkdHJlZYMBgwGDAYIEWCD_2rktIbM7CQPjMFxdtgoXDsQBe6ZnfYHz_VEkO7uCtYMCSGNhbmlzdGVygwGDAYMBgwJKAAAAAAAAAAcBAYMBgwGDAk5jZXJ0aWZpZWRfZGF0YYIDWCBWgGiPBtG2u85cDRFAPYs7vLdc6Grw571wSc0RMCI6EYIEWCDY9k96_KalXU7m3tmwIAusZlHK9MehkgIStaA8m_HfNoIEWCBOYkQtPpTRBP_gYe1cnFAcC8xgoKTJJTJlEp9i4bgOfoIEWCBthgttL0RxWKx9zQUzB1VVoDJfNVBABnygDRTTlPpcd4IEWCAfyKogUeU4zw3Pa2EBGB5VcNaukbds8ZT7AbSR6GX-gYIEWCDm4cUcLUxvcw_J2442dS94YI_tAHxqDsyTEqltTeRe4IIEWCBODzfzIXgSp1yunK4EJegz62agSuPIA2wlNiYf8RilioMBggRYICnLk_WKVCD1pHHANP7Kh0HvezdFZ1X21aGEwGM67W9ggwGCBFggfbVemnKYEja9TKRlj9yBlXGcvPfICHl9a299larerwSDAkR0aW1lggNJmrOgreu2wMwYaXNpZ25hdHVyZVgwqclPwHXkj30JDkbDXJZG5gddlh7PgmYEy5XS41RLyngn4KATI205Wt1cee-lKHXoamRlbGVnYXRpb26iaXN1Ym5ldF9pZFgdQ9yvEYDbgv2nCM46x6A6YGCr3hPpVGxg6MzmXQJrY2VydGlmaWNhdGVZApTZ2feiZHRyZWWDAYIEWCDtgh8WsbKpiZoLi5XYylCxX__YyZPUZsIk0PXjz1lzvYMBggRYIFunwSpsZKpJ5QG34-6Hdf5fiGjJrkCJRjDPdKogQZzlgwGDAkZzdWJuZXSDAYMBggRYIH78fWQs2KX21INhFAUooPeg_qG9FaEZ-FnJvQByCxnYgwGDAYMBgwGDAlgdQ9yvEYDbgv2nCM46x6A6YGCr3hPpVGxg6MzmXQKDAYMCT2NhbmlzdGVyX3Jhbmdlc4IDWDLZ2feCgkoAAAAAAAAABwEBSgAAAAAAAAAHAQGCSgAAAAACEAAAAQFKAAAAAAIf__8BAYMCSnB1YmxpY19rZXmCA1iFMIGCMB0GDSsGAQQBgtx8BQMBAgEGDCsGAQQBgtx8BQMCAQNhALiJ5Njs3jKCWgg15m1SnjGedDTM6N_jrO8RBSZZl13LZLkUsrJ-jXTvit3bHngQtgBp9qUJUzy8-XCxJo82dQVlV5gl1xnyg-ykrW4rt26B_Up2EY9NOZnuKxOqWdH8aYIEWCDNNRYZFlJHwcKUuJIXQK9JIA0pKYarBTb9K3DDUzpPD4IEWCCcu0ovvg1X5FqsJC-HA-rJOFf6CdjDxCqs4Ii-cSOwS4IEWCANOW8zq66lZnKBOKlXR-J03L7dcvb1a5pIDj7u4eHu54IEWCAmUyo7WsE_oDGyz13rElnLdnC06YydNtMc98fxHuNuDIIEWCB2Q0u6125BV4e6HKAOUmknD4_423JMC2hEXc2mwamhSoMCRHRpbWWCA0nPvfXihLHAzBhpc2lnbmF0dXJlWDCulZzovZrP2dWIIMhEBaSh5XU9J_aMKtKUW6JgNpzOTHAYDTCa1XyOdPpZ00e6ix9kdHJlZYMBggRYIHYbG1hqqpq8_WWQRV3C6xttakOgXO079nv_qXI6sETrgwJDc2lngwGCBFggqSaSUsJM66WL_J9fC3tzQBJsHddRM7iKFSHNW_dnjzuDAYIEWCCwKoJHyzof_3-jNNjHETV_ezpXKqNRTc76XGllr_3zHoMBggRYIJJpmDUOjCZuLgTeQwfKb0B7aJCbce1Nkt6svPUVwhpngwGCBFggRAxMN7esuXj82dGA0oo0jQFZL_E9_NIW3QZuNI8Zwx-DAYMBggRYIMGyphmMiwQtCOVs7007IKrTQdJMcTbReVp1F_LuJHxsgwGCBFggdHEIJNsCUNILPOaPC72zPGcDzhAPP3QeFCJHEo4C52yDAlgg5P8-40zMpjFL_rEmGbtJM39mBn6oASZiC_1kSmOgrEWDAlggJ8HNs_IqkCfk2fuw-rIgFqkpkSqPIxHFoVgIw4ysbaqCA0CCBFggwy969dGj_RU_qiA4zFkKgMgkmTw6Dg_uzIICxJCXkoFhaFggrQaFGeXvwG47mPtg184iJUpbfwDtMVhSJQinJDD7c6lhcFg-MDwwDAYKKwYBBAGDuEMBAgMsAAoAAAAAAAAABwEB4tq0V5C7RTXNzCnXExRTjoLBLwxA2yf_dlWf5jo_uFRhc1hAfcBZym1voNCADrdalYEZAWT_d_zlMVQG7X_X4lR9zTqvHxj-iwT0XBIN3nvHJIBx8wnxOOW2sOOu0hSpiYKgDQ";

        let se = SignedEnvelope::from_base64(msg).unwrap();
        println!("SignedEnvelope: {:?}", se);
        let rt = se.verify(1772449812590u64, None, None);
        println!("Verification result: {:?}", rt);
        assert!(rt.is_ok());

        // generated by ic-certification 3.1
        let msg = "pGFkgaJhZKJhZRsYprjiTQ1kp2FwWCwwKjAFBgMrZXADIQCA6d4AfqWkdFNu5tbaLp7r9jDxilfJvcLWRdIYzSior2FzWQc02dn3omtjZXJ0aWZpY2F0ZVkGWdnZ96NkdHJlZYMBgwGDAYIEWCD_2rktIbM7CQPjMFxdtgoXDsQBe6ZnfYHz_VEkO7uCtYMCSGNhbmlzdGVygwGDAYMBgwJKAAAAAAAAAAcBAYMBgwGDAk5jZXJ0aWZpZWRfZGF0YYIDWCB2gerHLFm4j1TskyiMYwuJVjti6AkhLhUkmf3yVzgt44IEWCDY9k96_KalXU7m3tmwIAusZlHK9MehkgIStaA8m_HfNoIEWCClii6jhbz6sPHd0uk34rJi6t4x2zU7ap7pu2pHQee1S4IEWCBthgttL0RxWKx9zQUzB1VVoDJfNVBABnygDRTTlPpcd4IEWCA7n254mzmU7xueyXf_BwN4PmMyXI3ZkEy2ep_1MtzWEYIEWCDSHJjM3167Pm98kHUeDMXaLF_cPTiJsu1zlQKZG7gnQoIEWCBZMddBC0-Quz23yaa-wGXhf4sJJ0CMVlNApcf6P0M2toMBggRYIIRNgDlknNZ00VrpChW1u2EkiJpLHm16Aaw9s81wxyw9gwGCBFggMm2roqFHTU5jlTeTnwz61Y9afsoeE1dBam7LurSMO1mDAkR0aW1lggNJp8mtrpTv4M4YaXNpZ25hdHVyZVgwsUfGmq1hpWv0RXnDU4isnnsrytYCYohlaDj8KpVrF95OaVWcsgyBWomgsYtLoxPqamRlbGVnYXRpb26iaXN1Ym5ldF9pZFgdQ9yvEYDbgv2nCM46x6A6YGCr3hPpVGxg6MzmXQJrY2VydGlmaWNhdGVZBBjZ2feiZHRyZWWDAYMBggRYIK3tAhG-5CMHRe6B6HYrumYzszgynK2_Cyk3lgxiz-VegwGDAk9jYW5pc3Rlcl9yYW5nZXODAYMBggRYIPWzPdcCUOwykUxZYJMI-zG0o1JPBtxcycR-ltOoWzQKgwGDAYMBgwGDAlgdQ9yvEYDbgv2nCM46x6A6YGCr3hPpVGxg6MzmXQKDAkoAAAAAAAAABwEBggNYMtnZ94KCSgAAAAAAAAAHAQFKAAAAAAAAAAcBAYJKAAAAAAIQAAABAUoAAAAAAh___wEBggRYIJ2wKoflpjM_8kD8PoQJl7Pvux0kvc21FZQlcEpPjebeggRYIGBtpBoQkI8sWSD0kMaU5qqTdy-kUVf1tmSbla42it92ggRYIOyolKEQ1iwzQOQhsmpRZ-DqycNrnX_QlOFSvAY74bT8ggRYIKvGtteIKoBa9myTiVb5LgymcFkhzR8PhHkWmb9ddOKMggRYIHddYNv5vlTceU_HgpFF5d20rbT5bWhGm-sx-i-4LQsLggRYIAH0KcEdL8U24W7AywFr3_rR_1KtobDR9S_4zxJRVn0UgwGCBFggeePKvLIybJiV9N45REvhjwjws9fpaA2eiaYKSPqhZ_yDAYMCRnN1Ym5ldIMBgwGCBFggiISkVrMM0Rk0jF71z1nHVHXloWSxxt03W_wPRhKANMKDAYMBgwGDAYMCWB1D3K8RgNuC_acIzjrHoDpgYKveE-lUbGDozOZdAoMBgwGCBFggUIlMwLMiEhS4oN4qyA9F8qOf6QkRzQ0IjhmShRyLJ-mDAkpwdWJsaWNfa2V5ggNYhTCBgjAdBg0rBgEEAYLcfAUDAQIBBgwrBgEEAYLcfAUDAgEDYQC4ieTY7N4ygloINeZtUp4xnnQ0zOjf46zvEQUmWZddy2S5FLKyfo1074rd2x54ELYAafalCVM8vPlwsSaPNnUFZVeYJdcZ8oPspK1uK7dugf1KdhGPTTmZ7isTqlnR_GmCBFggOtxzXIoxJu5vYSDBubz8MoJVDYZct-cjWYzLeTdyU5KCBFggNgnQcvmXshXCdPo1-Q72q0aowcpaEHxogyGI4WwCWPKCBFggj4x3eo7K7qsgb-JEuWw_9DcMe2RulRtusZKIgE7JCAyCBFgg2cEDpvexMjtphCR0PV8O8jGeRQXzwnGxohAmgy9KAbKCBFggYOlh9AO7NO3rkGDg86e0FJjFS5hp97IJEtK2fTj5Fo2CBFggOv_0dwIPh6D4tdK_kb9vQEszT-3myESFPY98--7BKfWDAkR0aW1lggNJo8Kk9ent4M4YaXNpZ25hdHVyZVgwhYPOO9jW_tQ5NAMfHRb2rICF9TnGmipUNRTkhaIOvsiy_VkrpQJGtSA7noPJuKEEZHRyZWWDAYIEWCAd1PxBigg-1t83kV57jr2buakKUEVB_liWTXLdBoAtOIMCQ3NpZ4MBgwGDAlggBYX1pW_0nbODY6WVI_XoWnz46JsL1yG2omXetIl1irWDAlggkxDcFnea8djFW-ISPfIwEdE-KvsXYYMq6_WnceSpqhmCA0CCBFggv3BAqPUThOCNdaS-f7TRhEu6A1izhQIvkbUeggveE7OCBFggRpEubsoN-OjKpzy8IAo9jGIQFCCVntAMhOVobFPyHJVhaFggrQaFGeXvwG47mPtg184iJUpbfwDtMVhSJQinJDD7c6lhcFg-MDwwDAYKKwYBBAGDuEMBAgMsAAoAAAAAAAAABwEBkVc8F_k6Ukkl1TQM_crFVBrhBzqDMR65DDzNGLoK-E5hc1hAsLo4H4SdoWumrYdekdP1oBIeOE-W5G8BanUak3w7TPCSZv1IBRu0iMW9x1AoA3EftD9ckUM_54hG21KCEknWCA";

        let se = SignedEnvelope::from_base64(msg).unwrap();
        println!("SignedEnvelope: {:?}", se);
        let rt = se.verify(1773718385139u64, None, None);
        println!("Verification result: {:?}", rt);
        assert!(rt.is_ok());
    }

    #[test]
    fn test_envelope_with_iccsa() {
        let msg = hex::decode("086c81b03b34184d2365b88a7d94ad9cc0f4e98970b6c10068aae4e407333339")
            .unwrap();
        let sig =
            hex::decode("d9d9f7a26b636572746966696361746558a1d9d9f7a26474726565830183024863616e697374657283024a0000000000000001010183024e6365727469666965645f646174618203582053e3b19ab292296b52b451b0662af2d86ac707569b39825fc31f62aca41406d483024474696d6582034387ad4b697369676e61747572655830a95766af95898e1c8492de7b7d9e6c601ea9d9958113f6c0491ef044ed5ebb03d31983abfa40ebbef7068ebaf7e66f05647472656583024373696783025820591047009df12cb39741d672f270045fd15beec2b0b84c1d71bda98b758726cd83025820d37372239856cdf2ae158e5ac365f15501a9e5612a970ddd7b3199c522b54194820340")
                .unwrap();
        let pk_der =
            hex::decode("303c300c060a2b0601040183b8430102032c000a000000000000000101011f809d0136deeed8e0187447d20ac0e13e0201e1dede8c437eada3e8dc349f85")
                .unwrap();
        let root =
            hex::decode("308182301d060d2b0601040182dc7c0503010201060c2b0601040182dc7c05030201036100b90210504fe157d1df412e500ced967ef794dc7aa88c84d764b74b6bc2cf0e575d79f331927df062240c88a28e1802c60b407c7bce541b50310d775919bcd0f799222c3738bc3bcc8bf05af5f52ee2afec54c460bda35c6c379267924db2d374")
                .unwrap();
        let (alg, _pk) = user_public_key_from_der(&pk_der).unwrap();
        assert_eq!(alg, Algorithm::IcCanisterSignature);

        let cspk = CanisterSigPublicKey::try_from(pk_der.as_slice()).unwrap();
        println!("canister_id: {}", cspk.canister_id.to_text());
        // canister_id: rrkah-fqaaa-aaaaa-aaaaq-cai

        let res = verify_sig_with_rootkey(&root, &pk_der, &msg, &sig, &0);
        println!("Verification result: {:?}", res);
        assert!(res.is_ok());
    }

    #[test]
    fn test_delegation_expiration_check_saturates_drift() {
        assert!(!is_delegation_expired(0, 0));
        assert!(!is_delegation_expired(0, PERMITTED_DRIFT_MS - 1));
        assert!(is_delegation_expired(0, PERMITTED_DRIFT_MS + 1));
    }

    #[test]
    fn test_base64_and_header_helpers_reject_invalid_input() {
        assert!(decode_base64("%%%").is_err());
        // both base64 alphabets are accepted, padded or not
        assert_eq!(decode_base64("-__v").unwrap(), vec![251, 255, 239]);
        assert_eq!(decode_base64("+//v").unwrap(), vec![251, 255, 239]);
        assert_eq!(decode_base64("+//v==").unwrap(), vec![251, 255, 239]);

        let mut headers = HeaderMap::new();
        headers.insert(&HEADER_IC_AUTH_PUBKEY, "%%%".parse().unwrap());
        assert_eq!(extract_data(&headers, &HEADER_IC_AUTH_PUBKEY), None);
        assert!(SignedEnvelope::from_headers(&headers).is_none());
        assert!(SignedEnvelope::from_authorization(&headers).is_none());

        headers.insert(AUTHORIZATION, "Bearer token".parse().unwrap());
        assert!(SignedEnvelope::from_authorization(&headers).is_none());
        headers.insert(AUTHORIZATION, "ICP invalid".parse().unwrap());
        assert!(SignedEnvelope::from_authorization(&headers).is_none());
    }

    #[test]
    fn test_envelope_roundtrips_authorization_and_component_headers() {
        let envelope = sample_envelope();
        let bytes = envelope.to_bytes();

        let decoded = SignedEnvelope::from_bytes(&bytes).unwrap();
        assert_eq!(decoded.pubkey, envelope.pubkey);
        assert_eq!(decoded.signature, envelope.signature);
        assert_eq!(decoded.digest, envelope.digest);
        assert!(SignedEnvelope::from_bytes(&[0xff]).is_err());

        let encoded = envelope.to_base64();
        let decoded = SignedEnvelope::from_base64(&encoded).unwrap();
        assert_eq!(decoded.pubkey, envelope.pubkey);

        let mut headers = HeaderMap::new();
        envelope.to_authorization(&mut headers).unwrap();
        let decoded = SignedEnvelope::from_authorization(&headers).unwrap();
        assert_eq!(decoded.signature, envelope.signature);

        let mut headers = HeaderMap::new();
        envelope.to_headers(&mut headers).unwrap();
        let decoded = SignedEnvelope::from_headers(&headers).unwrap();
        assert_eq!(decoded.pubkey, envelope.pubkey);
        assert_eq!(decoded.digest, envelope.digest);

        headers.remove(&HEADER_IC_AUTH_CONTENT_DIGEST);
        assert!(SignedEnvelope::from_headers(&headers).is_none());
    }

    #[test]
    fn test_envelope_headers_with_delegation_roundtrip() {
        let mut envelope = sample_envelope();
        envelope.delegation = Some(vec![SignedDelegationCompact {
            delegation: DelegationCompact {
                pubkey: vec![9, 8, 7].into(),
                expiration: u64::MAX,
                targets: Some(vec![Principal::management_canister()]),
                permissions: None,
            },
            signature: vec![6, 5, 4].into(),
        }]);

        let mut headers = HeaderMap::new();
        envelope.to_headers(&mut headers).unwrap();
        let decoded = SignedEnvelope::from_headers(&headers).unwrap();

        assert_eq!(decoded.delegation.unwrap().len(), 1);

        // a present but malformed delegation header invalidates the whole envelope
        headers.insert(&HEADER_IC_AUTH_DELEGATION, "%%%".parse().unwrap());
        assert!(SignedEnvelope::from_headers(&headers).is_none());

        // valid base64 but invalid CBOR is also rejected
        headers.insert(&HEADER_IC_AUTH_DELEGATION, "_w".parse().unwrap());
        assert!(SignedEnvelope::from_headers(&headers).is_none());
    }

    #[test]
    fn test_full_envelope_conversions_roundtrip() {
        let full = SignedEnvelopeFull {
            pubkey: vec![1, 2, 3].into(),
            signature: vec![4, 5, 6].into(),
            digest: Some(vec![7, 8, 9].into()),
            delegation: Some(vec![SignedDelegation {
                delegation: ic_auth_types::Delegation {
                    pubkey: vec![10, 11, 12].into(),
                    expiration: 123,
                    targets: Some(vec![Principal::management_canister()]),
                    permissions: None,
                },
                signature: vec![13, 14, 15].into(),
            }]),
        };

        let compact: SignedEnvelope = full.clone().into();
        assert_eq!(compact.pubkey, full.pubkey);
        assert_eq!(compact.signature, full.signature);
        assert_eq!(compact.digest, full.digest);
        assert_eq!(compact.delegation.as_ref().unwrap().len(), 1);

        let full_again: SignedEnvelopeFull = compact.into();
        assert_eq!(full_again.pubkey, full.pubkey);
        assert_eq!(full_again.signature, full.signature);
        assert_eq!(full_again.digest, full.digest);
        assert_eq!(full_again.delegation.unwrap().len(), 1);
    }

    #[cfg(feature = "identity")]
    #[test]
    fn test_targeted_delegation_verification_paths() {
        let user = BasicIdentity::from_raw_key(&[8u8; 32]);
        let session = BasicIdentity::from_raw_key(&[9u8; 32]);
        let target = Principal::management_canister();
        let expiration = unix_timestamp()
            .saturating_add(Duration::from_secs(3600))
            .as_nanos() as u64;
        let delegation = AgentDelegation {
            pubkey: session.public_key().unwrap(),
            expiration,
            targets: Some(vec![target]),
            permissions: None,
        };
        let signature = user
            .sign_delegation(&delegation)
            .unwrap()
            .signature
            .unwrap();
        let compact = SignedDelegationCompact {
            delegation: DelegationCompact {
                pubkey: delegation.pubkey.clone().into(),
                expiration: delegation.expiration,
                targets: delegation.targets.clone(),
                permissions: None,
            },
            signature: signature.clone().into(),
        };

        let user_pubkey = user.public_key().unwrap();
        let session_pubkey = session.public_key().unwrap();
        let now_ms = unix_timestamp().as_millis() as u64;
        verify_delegation_chain(
            &user_pubkey,
            &session_pubkey,
            std::slice::from_ref(&compact),
            now_ms,
            None,
        )
        .unwrap();
        let err = verify_delegation_chain(
            &user_pubkey,
            b"wrong-session",
            std::slice::from_ref(&compact),
            now_ms,
            None,
        )
        .unwrap_err();
        assert!(err.contains("Last verified public key does not match session public key"));

        let delegated = DelegatedIdentity::new_unchecked(
            user_pubkey.clone(),
            Box::new(session),
            vec![AgentSignedDelegation {
                delegation,
                signature,
            }],
        );
        let digest = sha3_256(b"targeted message");
        let envelope = SignedEnvelope::sign_digest(&delegated, digest.to_vec()).unwrap();
        envelope
            .verify(now_ms, Some(target), Some(&digest))
            .unwrap();
    }

    #[test]
    fn test_signed_envelope_rejects_delegation_length_and_expiration() {
        let delegation = SignedDelegationCompact {
            delegation: DelegationCompact {
                pubkey: vec![1, 2, 3].into(),
                expiration: u64::MAX,
                targets: None,
                permissions: None,
            },
            signature: vec![4, 5, 6].into(),
        };
        let envelope = SignedEnvelope {
            pubkey: vec![7, 8, 9].into(),
            signature: vec![10, 11, 12].into(),
            digest: Some(vec![13, 14, 15].into()),
            delegation: Some(vec![delegation; MAX_DELEGATION_CHAIN_LENGTH + 1]),
        };
        assert!(envelope.verify(0, None, Some(&[13, 14, 15])).is_err());
        let err = envelope.verify(0, None, None).unwrap_err();
        assert!(err.contains("Delegation chain length exceeds the limit"));

        let envelope = SignedEnvelope {
            pubkey: vec![7, 8, 9].into(),
            signature: vec![10, 11, 12].into(),
            digest: Some(vec![13, 14, 15].into()),
            delegation: Some(vec![SignedDelegationCompact {
                delegation: DelegationCompact {
                    pubkey: vec![1, 2, 3].into(),
                    expiration: 0,
                    targets: None,
                    permissions: None,
                },
                signature: vec![4, 5, 6].into(),
            }]),
        };
        let err = envelope
            .verify(PERMITTED_DRIFT_MS + 1, None, None)
            .unwrap_err();
        assert!(err.contains("Delegation has expired"));
    }

    #[test]
    fn test_extract_user_defaults_to_anonymous() {
        let mut headers = HeaderMap::new();
        assert_eq!(extract_user(&headers), ANONYMOUS_PRINCIPAL);

        headers.insert(
            &HEADER_IC_AUTH_USER,
            Principal::management_canister().to_text().parse().unwrap(),
        );
        assert_eq!(extract_user(&headers), Principal::management_canister());

        headers.insert(&HEADER_IC_AUTH_USER, "not-a-principal".parse().unwrap());
        assert_eq!(extract_user(&headers), ANONYMOUS_PRINCIPAL);
    }

    #[test]
    fn test_verify_rejects_missing_and_mismatched_digest_before_signature() {
        let envelope = SignedEnvelope {
            pubkey: vec![1, 2, 3].into(),
            signature: vec![4, 5, 6].into(),
            digest: None,
            delegation: None,
        };

        assert_eq!(
            envelope.verify(0, None, None).unwrap_err(),
            "No content digest provided for verification"
        );

        let mut envelope = sample_envelope();
        let err = envelope.verify(0, None, Some(&[0])).unwrap_err();
        assert_eq!(err, "Content digest does not match");

        envelope.digest = None;
        let err = envelope.verify(0, None, Some(&[7, 8, 9])).unwrap_err();
        assert!(err.contains("DER encoding") || err.contains("ASN.1"));
    }

    #[test]
    fn test_verify_rejects_delegation_target_mismatch() {
        let envelope = SignedEnvelope {
            pubkey: vec![1, 2, 3].into(),
            signature: vec![4, 5, 6].into(),
            digest: Some(vec![7, 8, 9].into()),
            delegation: Some(vec![SignedDelegationCompact {
                delegation: DelegationCompact {
                    pubkey: vec![9, 8, 7].into(),
                    expiration: u64::MAX,
                    targets: Some(vec![Principal::management_canister()]),
                    permissions: None,
                },
                signature: vec![6, 5, 4].into(),
            }]),
        };

        let other = Principal::from_slice(&[1, 2, 3]);
        let err = envelope.verify(0, Some(other), None).unwrap_err();
        assert!(err.contains("is not in the delegation targets"));
    }

    #[test]
    fn test_verify_delegation_chain_rejects_empty_and_invalid_chain() {
        assert_eq!(
            verify_delegation_chain(&[], &[], &[], 0, None).unwrap_err(),
            "Delegation chain is empty"
        );

        let delegation = SignedDelegationCompact {
            delegation: DelegationCompact {
                pubkey: vec![1, 2, 3].into(),
                expiration: u64::MAX,
                targets: None,
                permissions: None,
            },
            signature: vec![4, 5, 6].into(),
        };
        let err = verify_delegation_chain(&[], &[9], &[delegation], 0, Some(&[])).unwrap_err();
        assert!(!err.is_empty());
    }

    #[test]
    fn test_verify_delegation_chain_rejects_expired_delegation() {
        let delegation = SignedDelegationCompact {
            delegation: DelegationCompact {
                pubkey: vec![1, 2, 3].into(),
                expiration: 0,
                targets: None,
                permissions: None,
            },
            signature: vec![4, 5, 6].into(),
        };
        let err =
            verify_delegation_chain(&[], &[1, 2, 3], &[delegation], PERMITTED_DRIFT_MS + 1, None)
                .unwrap_err();
        assert!(err.contains("Delegation has expired"));
    }

    #[test]
    fn test_verify_delegation_chain_rejects_too_many_delegations() {
        let delegation = SignedDelegationCompact {
            delegation: DelegationCompact {
                pubkey: ByteBufB64::new(),
                expiration: u64::MAX,
                targets: None,
                permissions: None,
            },
            signature: ByteBufB64::new(),
        };
        let delegations = vec![delegation; MAX_DELEGATION_CHAIN_LENGTH + 1];

        let err = verify_delegation_chain(&[], &[], &delegations, 0, None).unwrap_err();
        assert!(err.contains("Delegation chain length exceeds the limit"));
    }

    #[cfg(any())]
    #[test]
    fn test_verify_delegation_chain() {
        let user_pubkey = hex::decode(
            "303C300C060A2B0601040183B8430102032C000A0000000000000007010116FB513D360579FA1102D36E3BC8D53FB966F3AC9F717842B2B54C227582D786",
        ).unwrap();
        let session_pubkey = hex::decode(
            "302A300506032B6570032100C6C020379C06F82F81111E1DA776F143C4F532EBE2D9FB16461F1243B5A92BAA",
        ).unwrap();
        let delegations = vec![
            SignedDelegationCompact {
                delegation: DelegationCompact {
                    pubkey: hex::decode(
                        "302A300506032B65700321005EC6DE6BD72919EA56CCA4E8E7124CEF75807DC212F1AE1FC3BA58903FC8795A",
                    ).unwrap().into(),
                    expiration: 1746365411593000000,
                    targets: None,
                },
                signature: hex::decode(
                    "D9D9F7A26B63657274696669636174655904AFD9D9F7A36474726565830183018301820458207F795EAF211FB3DA321D2291429BAD43A869A1CCFD761FF5A35F1A362322A1F483024863616E697374657283018301830183024A000000000000000701018301830183024E6365727469666965645F64617461820358201BD26E2A134BB173ACE579802B2BD138D28B3307CE5415BAD1304EDC8EB6A5E182045820D8F64F7AFCA6A55D4EE6DED9B0200BAC6651CAF4C7A1920212B5A03C9BF1DF3682045820F5ECD4D3EDCD85DE4C3B07B8A8D77F69DEFCB5BD652071FFD46B2A6604029112820458205085E5811DEACD817B4FF38A37C93B8421B7DDC92E5489FDF0D3F5CF36E320728204582032698C8D6A87E6831B3E8AA11641F27E77B692C6F866FD3D9D3E917EA570515682045820F7C9916E3BDEB2AC59E18441EBEFDDA60B801A91F8508B4F1B2ADA5B7F62AAA682045820989249384F8855B851E3F07C55E66DEFA4719612B1D9565E4C2B47918F902240830182045820FBF74833516364406FEC8290DFB4593E369120B408F757754CF11E96DF87AA8D83024474696D658203498CF3BBB0EDC2959E18697369676E61747572655830AF6204D323E367981055D0FB24E03070BDD6EC0D1BFABE2E3408B9C4792F1D4C44FE49A3AE9FD6ABD53D53DCF38E0E0E6A64656C65676174696F6EA2697375626E65745F6964581D43DCAF1180DB82FDA708CE3AC7A03A6060ABDE13E9546C60E8CCE65D026B6365727469666963617465590294D9D9F7A264747265658301820458200E077A1AA8E3A69E473446FB605EF74212198EC90D4D0010B3DC091AE3F01B798301830182045820954285DD391C0258F30BD79123B51AB939B93982F24426FE94E376FC13F5D3D38302467375626E6574830183018301820458200985315BBE905B7F9336D7064793905B005689F3C9C2A21AD9A31FBE6CDD5599830182045820466A70286CF9ACE9801CA53E22AF6EE059A094FD60498606D484B6854058307D83018301820458208B2F6C15078AE4D3B93470915CA53E373327F37EA74BA1B8177D986BB79B31AE8302581D43DCAF1180DB82FDA708CE3AC7A03A6060ABDE13E9546C60E8CCE65D02830183024F63616E69737465725F72616E67657382035832D9D9F782824A000000000000000701014A00000000000000070101824A000000000210000001014A00000000021FFFFF010183024A7075626C69635F6B657982035885308182301D060D2B0601040182DC7C0503010201060C2B0601040182DC7C050302010361008819AAA868DA353E3451BB97675FFFAA711E3C1C1230E39E1FEEB0AF9FE03E67C9393F08D796C1E42B528ABB5FCB4159199284B00096F6DAFA93B4711F1AC65F594B67CE2C0B35710E0391C5424CB754779A1C6084F6E77B584E7C8CF7FE9D89820458202C51DB7B5650B7A3DBBB8530A7449CC6F90144778B62F20F3C26D72E95E50698820458206961EF137C2AEE0B0467082EF6D3C12C03E93013B602A4CB6214270E484863F182045820EA6D87F551BD7F433852FA3F8697E653676C2DA59615618B39C50D74069123C983024474696D65820349A0BFA380BAC2959E18697369676E61747572655830B82F7761EC5C9A30672188D7A5F7EE82B540E51A86239C5A2A8E51DA6CB19CA3980DD8125440EC49C54865CED7EAA10F6474726565830182045820ECF5038E1037F1181243DAABBD82BE6045A5B0B9F1025C905FA339E2C1305608830243736967830183018204582029D9F6DAFC4411D79A0B7A64FCFF42FCE426974773A4178BA421DF59165D9E32830182045820ABD07F88910310B251F025105AEA620A46984E3D677A54EEC324134F6ED4DDD783018204582055D9BE42D0A247D96B4C67822E937EAD71567B5BE0A4965150F01D2AB51AC971830182045820CCB0E0C4819032E6C3C7B0E359F8A7297EED11A484FD7D8081CCA2461371E0328301830258206BB18EF359E4DBB2372D7C255BA9351BC862ACF06D9D2B14CDB1271EEFAA597E8302582015451785051A113622599AD74E94CC066EEF4BAC88AC36A42403C65CEDFFE8E0820340820458203A229EE09D8AC3B9C0540F596654FED78B3D5ABDD03657F66AD3240856214167820458206C0048C242D41CF83B77D782F6733B95B7AA137736741D05DACA0385D79BF466").unwrap().into()
                }, SignedDelegationCompact{
                    delegation: DelegationCompact {
                    pubkey: hex::decode(
                        "302A300506032B6570032100C6C020379C06F82F81111E1DA776F143C4F532EBE2D9FB16461F1243B5A92BAA",
                    ).unwrap().into(),
                    expiration: 1746365411593000000,
                    targets: None,
                },
                signature: hex::decode("18397232DD4AE43103E1884E956F91B44188E40A288DBCB73BF99DC27DBFAB1E1F0FAB76C44E0A0206F34887D5197B46C2D57876B0DB4C28E97967FDA8807908").unwrap().into(),
                }];
        let rt = verify_delegation_chain(
            &user_pubkey,
            &session_pubkey,
            &delegations,
            1746365411593,
            None,
        );
        println!("Verification result: {:?}", rt);
        assert!(rt.is_ok(), "Delegation chain verification failed: {:?}", rt);

        let err = verify_delegation_chain(
            &user_pubkey,
            b"wrong-session",
            &delegations,
            1746365411593,
            None,
        )
        .unwrap_err();
        assert!(err.contains("Last verified public key does not match session public key"));
    }
}
