//! Asymmetric key example — Ed25519 sign/verify, ECDH key agreement,
//! and RSA-OAEP encrypt/decrypt.
//!
//! Run with: cargo run --example pkey -p native-ossl

use native_ossl::pkey::{
    DeriveCtx, KeygenCtx, PkeyDecryptCtx, PkeyEncryptCtx, SignInit, Signer, Verifier,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    ed25519_sign_verify()?;
    ecdh_derive()?;
    rsa_oaep()?;
    Ok(())
}

// ── Ed25519 sign / verify ─────────────────────────────────────────────────────

fn ed25519_sign_verify() -> Result<(), Box<dyn std::error::Error>> {
    let message = b"sign this message";

    // Generate a key pair.
    let mut kgen = KeygenCtx::new(c"ED25519")?;
    let priv_key = kgen.generate()?;

    // Sign — Ed25519 hashes internally; use Signer (EVP_DigestSign), not RawSigner.
    let init = SignInit::default();
    let mut signer = Signer::new(&priv_key, &init)?;
    let sig = signer.sign_oneshot(message)?;
    println!(
        "Ed25519 signature ({} bytes): {}",
        sig.len(),
        native_ossl::util::hex_encode(&sig)
    );

    // Verify with the public key.
    let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(priv_key.clone());
    let mut verifier = Verifier::new(&pub_key, &init)?;
    verifier.verify_oneshot(message, &sig)?;
    println!("Ed25519 verify: OK");

    // Tampered message must fail.
    let mut bad = message.to_vec();
    bad[0] ^= 1;
    let mut verifier2 = Verifier::new(&pub_key, &init)?;
    assert!(!verifier2.verify_oneshot(&bad, &sig)?);
    println!("Ed25519 tampered message rejected: OK");

    // PEM round-trip.
    let pem = priv_key.to_pem()?;
    let reloaded = native_ossl::pkey::Pkey::<native_ossl::pkey::Private>::from_pem(&pem)?;
    assert!(priv_key.public_eq(&reloaded));
    println!("Ed25519 PEM round-trip: OK");

    Ok(())
}

// ── ECDH key agreement ────────────────────────────────────────────────────────

fn ecdh_derive() -> Result<(), Box<dyn std::error::Error>> {
    // Generate two P-256 key pairs.
    let mut kgen = KeygenCtx::new(c"EC")?;
    let params = native_ossl::params::ParamBuilder::new()?
        .push_utf8_string(c"group", c"P-256")?
        .build()?;
    kgen.set_params(&params)?;

    let alice_priv = kgen.generate()?;
    let bob_priv = kgen.generate()?;

    let alice_pub = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(alice_priv.clone());
    let bob_pub = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(bob_priv.clone());

    // Alice derives with her private key and Bob's public key.
    let mut alice_ctx = DeriveCtx::new(&alice_priv)?;
    alice_ctx.set_peer(&bob_pub)?;
    let shared_len = alice_ctx.derive_len()?;
    let mut alice_shared = vec![0u8; shared_len];
    alice_ctx.derive(&mut alice_shared)?;

    // Bob derives with his private key and Alice's public key.
    let mut bob_ctx = DeriveCtx::new(&bob_priv)?;
    bob_ctx.set_peer(&alice_pub)?;
    let mut bob_shared = vec![0u8; shared_len];
    bob_ctx.derive(&mut bob_shared)?;

    assert_eq!(alice_shared, bob_shared, "ECDH shared secrets must match");
    println!(
        "ECDH P-256 shared secret ({} bytes): {}",
        alice_shared.len(),
        native_ossl::util::hex_encode(&alice_shared)
    );

    Ok(())
}

// ── RSA-OAEP encrypt / decrypt ────────────────────────────────────────────────

fn rsa_oaep() -> Result<(), Box<dyn std::error::Error>> {
    // Generate a 2048-bit RSA key pair.
    let mut kgen = KeygenCtx::new(c"RSA")?;
    let params = native_ossl::params::ParamBuilder::new()?
        .push_uint(c"bits", 2048)?
        .build()?;
    kgen.set_params(&params)?;
    let priv_key = kgen.generate()?;
    let pub_key = native_ossl::pkey::Pkey::<native_ossl::pkey::Public>::from(priv_key.clone());

    println!("RSA key size: {} bits", priv_key.bits());

    // Configure OAEP padding.
    let oaep_params = native_ossl::params::ParamBuilder::new()?
        .push_utf8_string(c"pad-mode", c"oaep")?
        .push_utf8_string(c"digest", c"SHA2-256")?
        .build()?;

    // Encrypt with the public key.
    let plaintext = b"wrap a 32-byte symmetric key here";
    let mut enc_ctx = PkeyEncryptCtx::new(&pub_key, Some(&oaep_params))?;
    let ct_len = enc_ctx.encrypt_len(plaintext.len())?;
    let mut ciphertext = vec![0u8; ct_len];
    let written = enc_ctx.encrypt(plaintext, &mut ciphertext)?;
    ciphertext.truncate(written);
    println!("RSA-OAEP ciphertext: {} bytes", ciphertext.len());

    // Decrypt with the private key.
    let mut dec_ctx = PkeyDecryptCtx::new(&priv_key, Some(&oaep_params))?;
    // Output buffer must be at least as large as the RSA key size, not the plaintext size.
    let mut recovered = vec![0u8; ciphertext.len()];
    let n = dec_ctx.decrypt(&ciphertext, &mut recovered)?;
    recovered.truncate(n);

    assert_eq!(&recovered, plaintext);
    println!("RSA-OAEP round-trip: OK");

    Ok(())
}
