//! Python bindings for the Irene Cipher library
//! 
//! This module exposes a clean, Pythonic API for the Rust cryptographic
//! functionality using PyO3.

use crate::crypto;
use crate::error::CryptoError;
use crate::file_ops;
use pyo3::exceptions::{PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use pyo3::types::PyModule;

/// Convert Rust CryptoError to Python exception
impl From<CryptoError> for PyErr {
    fn from(err: CryptoError) -> PyErr {
        match err {
            CryptoError::EncryptionError(msg) => PyRuntimeError::new_err(format!("Encryption failed: {}", msg)),
            CryptoError::DecryptionError(msg) => PyRuntimeError::new_err(format!("Decryption failed: {}", msg)),
            CryptoError::AuthenticationFailed => {
                PyValueError::new_err("Authentication failed: incorrect password or tampered data")
            }
            CryptoError::InvalidKeyLength { expected, actual } => {
                PyValueError::new_err(format!("Invalid key length: expected {}, got {}", expected, actual))
            }
            CryptoError::InvalidFormat(msg) => PyValueError::new_err(format!("Invalid format: {}", msg)),
            CryptoError::IoError(err) => PyRuntimeError::new_err(format!("I/O error: {}", err)),
            CryptoError::Utf8Error(err) => PyValueError::new_err(format!("UTF-8 error: {}", err)),
            CryptoError::KeyDerivationError(msg) => {
                PyRuntimeError::new_err(format!("Key derivation failed: {}", msg))
            }
        }
    }
}

/// Generate a secure random encryption key.
/// 
/// Returns a 32-byte key suitable for direct use with XChaCha20-Poly1305.
/// 
/// Returns:
///     bytes: A 32-byte cryptographically secure random key
/// 
/// Example:
///     >>> import irene_cipher
///     >>> key = irene_cipher.generate_key()
///     >>> len(key)
///     32
#[pyfunction]
fn generate_key(py: Python) -> PyResult<Py<PyBytes>> {
    let key = crypto::generate_key();
    Ok(PyBytes::new_bound(py, &key).unbind())
}

/// Encrypt text using password-based encryption.
/// 
/// Uses XChaCha20-Poly1305 (AEAD) with Argon2id key derivation.
/// Each encryption uses a unique random salt and nonce, so encrypting
/// the same text twice produces different ciphertext.
/// 
/// Args:
///     text (str): The plaintext string to encrypt
///     password (str): Password for key derivation
/// 
/// Returns:
///     bytes: Encrypted data with salt and nonce prepended
/// 
/// Raises:
///     RuntimeError: If encryption fails
/// 
/// Example:
///     >>> import irene_cipher
///     >>> ciphertext = irene_cipher.encrypt_text("Hello, World!", "my_password")
///     >>> type(ciphertext)
///     <class 'bytes'>
#[pyfunction]
fn encrypt_text(py: Python, text: &str, password: &str) -> PyResult<Py<PyBytes>> {
    let ciphertext = crypto::encrypt_bytes(text.as_bytes(), password)?;
    Ok(PyBytes::new_bound(py, &ciphertext).unbind())
}

/// Decrypt text encrypted with encrypt_text.
/// 
/// Verifies the authentication tag and returns the original plaintext.
/// Will fail if the password is incorrect or if the ciphertext has been tampered with.
/// 
/// Args:
///     data (bytes): Encrypted data from encrypt_text
///     password (str): Password used for encryption
/// 
/// Returns:
///     str: The decrypted plaintext string
/// 
/// Raises:
///     ValueError: If password is incorrect or data has been tampered with
///     RuntimeError: If decryption fails for other reasons
/// 
/// Example:
///     >>> import irene_cipher
///     >>> ciphertext = irene_cipher.encrypt_text("Secret", "password")
///     >>> plaintext = irene_cipher.decrypt_text(ciphertext, "password")
///     >>> plaintext
///     'Secret'
#[pyfunction]
fn decrypt_text(data: &[u8], password: &str) -> PyResult<String> {
    let plaintext = crypto::decrypt_bytes(data, password)?;
    let text = String::from_utf8(plaintext)?;
    Ok(text)
}

/// Encrypt bytes using password-based encryption.
/// 
/// Similar to encrypt_text but works with raw bytes instead of strings.
/// Useful for encrypting binary data like images, PDFs, etc.
/// 
/// Args:
///     data (bytes): The data to encrypt
///     password (str): Password for key derivation
/// 
/// Returns:
///     bytes: Encrypted data with salt and nonce prepended
/// 
/// Raises:
///     RuntimeError: If encryption fails
/// 
/// Example:
///     >>> import irene_cipher
///     >>> data = b"\\x00\\x01\\x02\\x03"
///     >>> encrypted = irene_cipher.encrypt_bytes(data, "password")
///     >>> type(encrypted)
///     <class 'bytes'>
#[pyfunction]
fn encrypt_bytes(py: Python, data: &[u8], password: &str) -> PyResult<Py<PyBytes>> {
    let ciphertext = crypto::encrypt_bytes(data, password)?;
    Ok(PyBytes::new_bound(py, &ciphertext).unbind())
}

/// Decrypt bytes encrypted with encrypt_bytes.
/// 
/// Args:
///     data (bytes): Encrypted data from encrypt_bytes
///     password (str): Password used for encryption
/// 
/// Returns:
///     bytes: The decrypted data
/// 
/// Raises:
///     ValueError: If password is incorrect or data has been tampered with
///     RuntimeError: If decryption fails for other reasons
/// 
/// Example:
///     >>> import irene_cipher
///     >>> original = b"\\x00\\x01\\x02\\x03"
///     >>> encrypted = irene_cipher.encrypt_bytes(original, "password")
///     >>> decrypted = irene_cipher.decrypt_bytes(encrypted, "password")
///     >>> decrypted == original
///     True
#[pyfunction]
fn decrypt_bytes(py: Python, data: &[u8], password: &str) -> PyResult<Py<PyBytes>> {
    let plaintext = crypto::decrypt_bytes(data, password)?;
    Ok(PyBytes::new_bound(py, &plaintext).unbind())
}

/// Encrypt a file using password-based encryption.
/// 
/// Encrypts files of any size using streaming/chunked encryption, so even
/// multi-GB files can be encrypted without loading them entirely into memory.
/// 
/// The encrypted file has a custom format with salt, nonce, and encrypted chunks.
/// Always use decrypt_file to decrypt files encrypted with this function.
/// 
/// Args:
///     input_path (str): Path to the file to encrypt
///     output_path (str): Path where the encrypted file will be written
///     password (str): Password for key derivation
/// 
/// Raises:
///     RuntimeError: If encryption or I/O fails
///     FileNotFoundError: If input file doesn't exist
/// 
/// Example:
///     >>> import irene_cipher
///     >>> irene_cipher.encrypt_file("document.pdf", "document.pdf.enc", "my_password")
/// 
/// Security Notes:
///     - Each file uses a unique random salt and nonce
///     - The entire file is authenticated (tampering will be detected)
///     - Memory usage stays constant regardless of file size
#[pyfunction]
fn encrypt_file(input_path: &str, output_path: &str, password: &str) -> PyResult<()> {
    file_ops::encrypt_file(input_path, output_path, password)?;
    Ok(())
}

/// Decrypt a file encrypted with encrypt_file.
/// 
/// Decrypts files using streaming/chunked decryption for memory efficiency.
/// Verifies authentication tags for each chunk, ensuring the file hasn't
/// been tampered with.
/// 
/// Args:
///     input_path (str): Path to the encrypted file
///     output_path (str): Path where the decrypted file will be written
///     password (str): Password used for encryption
/// 
/// Raises:
///     ValueError: If password is incorrect or file has been tampered with
///     RuntimeError: If decryption or I/O fails
///     FileNotFoundError: If input file doesn't exist
/// 
/// Example:
///     >>> import irene_cipher
///     >>> irene_cipher.decrypt_file("document.pdf.enc", "document.pdf", "my_password")
/// 
/// Security Notes:
///     - Decryption will fail if any chunk has been modified
///     - File size is verified against the header
///     - Memory usage stays constant regardless of file size
#[pyfunction]
fn decrypt_file(input_path: &str, output_path: &str, password: &str) -> PyResult<()> {
    file_ops::decrypt_file(input_path, output_path, password)?;
    Ok(())
}

/// Irene Cipher - Production-grade cryptographic library
/// 
/// A high-performance cryptographic library that provides secure encryption
/// and decryption for text and files using modern, audited algorithms.
/// 
/// Algorithms:
///     - Encryption: XChaCha20-Poly1305 (AEAD cipher)
///     - Key Derivation: Argon2id (memory-hard)
///     - Key Expansion: HKDF-SHA256
/// 
/// Features:
///     - Text and binary data encryption
///     - File encryption with streaming (handles multi-GB files)
///     - Password-based encryption
///     - Authenticated encryption (prevents tampering)
///     - Secure random key generation
/// 
/// Security Properties:
///     - No deprecated or insecure algorithms
///     - Constant-time comparisons where needed
///     - Unique nonces for every encryption
///     - Memory-hard password hashing (GPU resistant)
/// 
/// Example:
///     >>> import irene_cipher
///     >>> 
///     >>> # Encrypt text
///     >>> ciphertext = irene_cipher.encrypt_text("Hello!", "password")
///     >>> plaintext = irene_cipher.decrypt_text(ciphertext, "password")
///     >>> 
///     >>> # Encrypt files
///     >>> irene_cipher.encrypt_file("data.pdf", "data.pdf.enc", "password")
///     >>> irene_cipher.decrypt_file("data.pdf.enc", "data.pdf", "password")
#[pymodule]
fn irene_cipher(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(generate_key, m)?)?;
    m.add_function(wrap_pyfunction!(encrypt_text, m)?)?;
    m.add_function(wrap_pyfunction!(decrypt_text, m)?)?;
    m.add_function(wrap_pyfunction!(encrypt_bytes, m)?)?;
    m.add_function(wrap_pyfunction!(decrypt_bytes, m)?)?;
    m.add_function(wrap_pyfunction!(encrypt_file, m)?)?;
    m.add_function(wrap_pyfunction!(decrypt_file, m)?)?;
    Ok(())
}
