//! Hybrid signature convenience API (ML-DSA-65 + Ed25519)
//!
//! This module wraps [`crate::hybrid::sig_hybrid`] to provide a high-level hybrid
//! signature API with `SecurityMode` support. The underlying implementation uses
//! AND-composition: both ML-DSA and Ed25519 must verify for the signature to be valid.
//!
//! ## Usage
//!
//! ```no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! use latticearc::unified_api::{
//!     generate_hybrid_signing_keypair, sign_hybrid, verify_hybrid_signature,
//!     SecurityMode,
//! };
//!
//! let (pk, sk) = generate_hybrid_signing_keypair(SecurityMode::Unverified)?;
//! let signature = sign_hybrid(b"message", &sk, SecurityMode::Unverified)?;
//! let valid = verify_hybrid_signature(b"message", &signature, &pk, SecurityMode::Unverified)?;
//! assert!(valid);
//! # Ok(())
//! # }
//! ```

use crate::hybrid::sig_hybrid::{
    self, HybridSigPublicKey, HybridSigSecretKey, HybridSignature, HybridSignatureError,
};

use crate::unified_api::error::{CoreError, Result};
use crate::unified_api::zero_trust::SecurityMode;

use crate::primitives::resource_limits::validate_signature_size;

/// Generate a hybrid signing keypair (ML-DSA-65 + Ed25519).
///
/// Returns a public key (for verification) and a secret key (for signing).
/// The keypair combines post-quantum and classical algorithms so that
/// security holds if *either* algorithm remains secure.
///
/// # Errors
///
/// Returns an error if:
/// - The session has expired (when `mode` is `Verified`)
/// - Key generation fails
#[must_use = "generated keypair must be stored or used"]
pub fn generate_hybrid_signing_keypair(
    mode: SecurityMode,
) -> Result<(HybridSigPublicKey, HybridSigSecretKey)> {
    mode.validate()?;

    sig_hybrid::generate_keypair().map_err(|e| {
        CoreError::SignatureFailed(format!("Hybrid signing keypair generation failed: {e}"))
    })
}

/// Sign a message using hybrid signatures (ML-DSA-65 + Ed25519).
///
/// Both ML-DSA and Ed25519 signatures are generated. An attacker would need
/// to break *both* algorithms to forge a signature.
///
/// # Errors
///
/// Returns an error if:
/// - The session has expired (when `mode` is `Verified`)
/// - Message size exceeds resource limits
/// - Signing fails (invalid key material or crypto error)
pub fn sign_hybrid(
    message: &[u8],
    sk: &HybridSigSecretKey,
    mode: SecurityMode,
) -> Result<HybridSignature> {
    mode.validate()?;

    // opaque ResourceExceeded — sign-side
    // resource-limit errors stay loud; caller controls input.
    if let Err(e) = validate_signature_size(message.len()) {
        tracing::debug!(error = %e, msg_len = message.len(), "hybrid sig rejected: message exceeds resource limit");
        return Err(CoreError::ResourceExceeded("message exceeds resource limit".to_string()));
    }

    sig_hybrid::sign(sk, message).map_err(hybrid_sig_error_to_core)
}

/// Verify a hybrid signature (ML-DSA-65 + Ed25519).
///
/// Both component signatures must verify for the result to be `Ok(true)`.
///
/// # Errors
///
/// Returns an error if:
/// - The session has expired (when `mode` is `Verified`)
/// - Message size exceeds resource limits
/// - Key or signature material is malformed
/// - Either component verification fails
pub fn verify_hybrid_signature(
    message: &[u8],
    signature: &HybridSignature,
    pk: &HybridSigPublicKey,
    mode: SecurityMode,
) -> Result<bool> {
    mode.validate()?;

    // collapse to `Ok(false)` on the
    // verify path so an adversary cannot binary-search the configured
    // cap from the Result variant.
    if let Err(e) = validate_signature_size(message.len()) {
        tracing::debug!(error = %e, msg_len = message.len(), "hybrid sig verify rejected: message exceeds resource limit");
        return Ok(false);
    }

    // every adversary-reachable verify failure
    // collapses to `Ok(false)` here. The previous mapping went through
    // `hybrid_sig_error_to_core`, which kept distinguishable
    // `Hybrid ML-DSA error: ...` vs `Hybrid Ed25519 error: ...`
    // strings — an adversary injecting malformed bytes for one half
    // could learn which component rejected. The sign-side mapper
    // (`hybrid_sig_error_to_core`) still keeps diagnostics for sign-side
    // failures where the SK holder is presumed authorized.
    match sig_hybrid::verify(pk, message, signature) {
        Ok(b) => Ok(b),
        Err(e) => {
            tracing::debug!(error = %e, "hybrid sig verify rejected (Err mapped to Ok(false))");
            Ok(false)
        }
    }
}

/// Generate a hybrid signing keypair with configuration validation.
///
/// # Errors
///
/// Returns an error if config validation, session validation, or key generation fails.
#[inline]
#[must_use = "generated keypair must be stored or used"]
pub fn generate_hybrid_signing_keypair_with_config(
    config: &crate::unified_api::CoreConfig,
    mode: SecurityMode,
) -> Result<(HybridSigPublicKey, HybridSigSecretKey)> {
    config.validate()?;
    generate_hybrid_signing_keypair(mode)
}

/// Sign a message using hybrid signatures with configuration validation.
///
/// # Errors
///
/// Returns an error if config validation, session validation, or signing fails.
#[inline]
pub fn sign_hybrid_with_config(
    message: &[u8],
    sk: &HybridSigSecretKey,
    config: &crate::unified_api::CoreConfig,
    mode: SecurityMode,
) -> Result<HybridSignature> {
    config.validate()?;
    sign_hybrid(message, sk, mode)
}

/// Verify a hybrid signature with configuration validation.
///
/// # Errors
///
/// Returns an error if config validation, session validation, or verification fails.
#[inline]
pub fn verify_hybrid_signature_with_config(
    message: &[u8],
    signature: &HybridSignature,
    pk: &HybridSigPublicKey,
    config: &crate::unified_api::CoreConfig,
    mode: SecurityMode,
) -> Result<bool> {
    config.validate()?;
    verify_hybrid_signature(message, signature, pk, mode)
}

// ============================================================================
// Unverified Convenience Variants — see `convenience::mod` docs for the
// shared security guidance on when to use `_unverified` variants.
// ============================================================================

/// Generate a hybrid signing keypair without Zero Trust verification.
///
/// # Errors
///
/// Returns an error if key generation fails.
#[inline]
#[must_use = "generated keypair must be stored or used"]
pub fn generate_hybrid_signing_keypair_unverified()
-> Result<(HybridSigPublicKey, HybridSigSecretKey)> {
    generate_hybrid_signing_keypair(SecurityMode::Unverified)
}

/// Sign a message using hybrid signatures without Zero Trust verification.
///
/// # Errors
///
/// Returns an error if signing fails.
#[inline]
pub fn sign_hybrid_unverified(message: &[u8], sk: &HybridSigSecretKey) -> Result<HybridSignature> {
    sign_hybrid(message, sk, SecurityMode::Unverified)
}

/// Verify a hybrid signature without Zero Trust verification.
///
/// # Errors
///
/// Returns an error if verification fails.
#[inline]
pub fn verify_hybrid_signature_unverified(
    message: &[u8],
    signature: &HybridSignature,
    pk: &HybridSigPublicKey,
) -> Result<bool> {
    verify_hybrid_signature(message, signature, pk, SecurityMode::Unverified)
}

/// Convert `HybridSignatureError` to `CoreError`.
///
/// DP-M3 fix: the sign-path opaque `SigningFailed` variant maps to
/// `CoreError::SignatureFailed` with a non-component-identifying message
/// so the Pattern-6 opacity holds through the conversion. The retained
/// `MlDsaError` / `Ed25519Error` variants here cover the keygen and
/// other non-adversary-reachable paths where component identification is
/// useful for operator diagnostics; they nevertheless route into the
/// same `CoreError::SignatureFailed` variant so external callers can't
/// distinguish stage at the `CoreError` layer either.
fn hybrid_sig_error_to_core(e: HybridSignatureError) -> CoreError {
    match e {
        HybridSignatureError::SigningFailed(msg) => {
            CoreError::SignatureFailed(format!("Hybrid signing failed: {msg}"))
        }
        HybridSignatureError::MlDsaError(msg) => {
            CoreError::SignatureFailed(format!("Hybrid ML-DSA error: {msg}"))
        }
        HybridSignatureError::Ed25519Error(msg) => {
            CoreError::SignatureFailed(format!("Hybrid Ed25519 error: {msg}"))
        }
        HybridSignatureError::VerificationFailed(_msg) => CoreError::VerificationFailed,
        HybridSignatureError::InvalidKeyMaterial(msg) => {
            CoreError::InvalidKey(format!("Hybrid key material error: {msg}"))
        }
        HybridSignatureError::CryptoError(msg) => {
            CoreError::SignatureFailed(format!("Hybrid crypto error: {msg}"))
        }
    }
}

#[cfg(test)]
#[expect(
    clippy::panic_in_result_fn,
    reason = "test/bench scaffolding: lints suppressed for this module"
)]
mod tests {
    use super::*;

    #[test]
    fn test_hybrid_sig_roundtrip_unverified_roundtrip() -> Result<()> {
        let (pk, sk) = generate_hybrid_signing_keypair_unverified()?;

        let message = b"Hello, hybrid signatures!";
        let signature = sign_hybrid_unverified(message, &sk)?;
        let valid = verify_hybrid_signature_unverified(message, &signature, &pk)?;

        assert!(valid, "Hybrid signature roundtrip should succeed");
        Ok(())
    }

    #[test]
    fn test_hybrid_sig_roundtrip_with_mode_roundtrip() -> Result<()> {
        let (pk, sk) = generate_hybrid_signing_keypair(SecurityMode::Unverified)?;

        let message = b"SecurityMode test";
        let signature = sign_hybrid(message, &sk, SecurityMode::Unverified)?;
        let valid = verify_hybrid_signature(message, &signature, &pk, SecurityMode::Unverified)?;

        assert!(valid);
        Ok(())
    }

    #[test]
    fn test_hybrid_sig_with_config_succeeds() -> Result<()> {
        let config = crate::unified_api::CoreConfig::default();
        let (pk, sk) =
            generate_hybrid_signing_keypair_with_config(&config, SecurityMode::Unverified)?;

        let message = b"Config test";
        let signature = sign_hybrid_with_config(message, &sk, &config, SecurityMode::Unverified)?;
        let valid = verify_hybrid_signature_with_config(
            message,
            &signature,
            &pk,
            &config,
            SecurityMode::Unverified,
        )?;

        assert!(valid);
        Ok(())
    }

    #[test]
    fn test_hybrid_sig_wrong_message_fails() -> Result<()> {
        let (pk, sk) = generate_hybrid_signing_keypair_unverified()?;

        let signature = sign_hybrid_unverified(b"correct message", &sk)?;
        let result = verify_hybrid_signature_unverified(b"wrong message", &signature, &pk);

        // verify path collapses Err to Ok(false) (Pattern 6).
        assert_eq!(result.ok(), Some(false), "Wrong message must yield Ok(false), not Err");
        Ok(())
    }

    #[test]
    fn test_hybrid_sig_wrong_key_fails() -> Result<()> {
        let (_pk1, sk1) = generate_hybrid_signing_keypair_unverified()?;
        let (pk2, _sk2) = generate_hybrid_signing_keypair_unverified()?;

        let message = b"cross-key test";
        let signature = sign_hybrid_unverified(message, &sk1)?;
        let result = verify_hybrid_signature_unverified(message, &signature, &pk2);

        // verify path collapses Err to Ok(false) (Pattern 6).
        assert_eq!(result.ok(), Some(false), "Wrong key must yield Ok(false), not Err");
        Ok(())
    }

    #[test]
    fn test_hybrid_sig_empty_message_succeeds() -> Result<()> {
        let (pk, sk) = generate_hybrid_signing_keypair_unverified()?;

        let message = b"";
        let signature = sign_hybrid_unverified(message, &sk)?;
        let valid = verify_hybrid_signature_unverified(message, &signature, &pk)?;

        assert!(valid, "Empty message should sign and verify");
        Ok(())
    }

    #[test]
    fn test_hybrid_sig_large_message_succeeds() -> Result<()> {
        let (pk, sk) = generate_hybrid_signing_keypair_unverified()?;

        let message = vec![0xAB; 10_000];
        let signature = sign_hybrid_unverified(&message, &sk)?;
        let valid = verify_hybrid_signature_unverified(&message, &signature, &pk)?;

        assert!(valid, "Large message should sign and verify");
        Ok(())
    }

    #[test]
    fn test_hybrid_sig_verified_session_succeeds() -> Result<()> {
        let (auth_pk, auth_sk) = crate::unified_api::convenience::keygen::generate_keypair()?;
        let session = crate::unified_api::zero_trust::VerifiedSession::establish(
            auth_pk.as_slice(),
            auth_sk.expose_secret(),
        )?;

        let (pk, sk) = generate_hybrid_signing_keypair(SecurityMode::Verified(&session))?;

        let message = b"Verified session test";
        let signature = sign_hybrid(message, &sk, SecurityMode::Verified(&session))?;
        let valid =
            verify_hybrid_signature(message, &signature, &pk, SecurityMode::Verified(&session))?;

        assert!(valid);
        Ok(())
    }
}
