//! Python bindings for authentication crypto operations
//! 
//! Exposes Rust cryptographic primitives to Python with a clean,
//! Pythonic API for use in FastAPI applications.

use crate::error::AuthError;
use crate::{jwt, password, token};
use pyo3::exceptions::{PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::PyDict;
use std::collections::HashMap;

/// Convert Rust AuthError to Python exception
impl From<AuthError> for PyErr {
    fn from(err: AuthError) -> PyErr {
        match err {
            AuthError::PasswordVerificationFailed => {
                PyValueError::new_err("Password verification failed")
            }
            AuthError::InvalidPasswordHashFormat => {
                PyValueError::new_err("Invalid password hash format")
            }
            AuthError::TokenExpired => PyValueError::new_err("Token has expired"),
            AuthError::InvalidToken => PyValueError::new_err("Invalid token"),
            AuthError::InvalidAlgorithm(msg) => PyValueError::new_err(format!("Invalid algorithm: {}", msg)),
            _ => PyRuntimeError::new_err(err.to_string()),
        }
    }
}

/// Hash a password using Argon2id
/// 
/// Args:
///     password (str): The plaintext password to hash
/// 
/// Returns:
///     str: PHC formatted password hash
/// 
/// Raises:
///     RuntimeError: If hashing fails
/// 
/// Example:
///     >>> import irene_auth
///     >>> password_hash = irene_auth.hash_password("my_secure_password")
///     >>> print(password_hash[:20])
///     $argon2id$v=19$m=6...
#[pyfunction]
fn hash_password(password: &str) -> PyResult<String> {
    password::hash_password(password).map_err(PyErr::from)
}

/// Verify a password against a hash
/// 
/// Args:
///     password (str): The plaintext password to verify
///     password_hash (str): The PHC formatted hash to check against
/// 
/// Returns:
///     bool: True if password matches
/// 
/// Raises:
///     ValueError: If password doesn't match or hash is invalid
/// 
/// Example:
///     >>> import irene_auth
///     >>> hash = irene_auth.hash_password("correct_password")
///     >>> irene_auth.verify_password("correct_password", hash)
///     True
///     >>> irene_auth.verify_password("wrong_password", hash)
///     Traceback (most recent call last):
///     ...
///     ValueError: Password verification failed
#[pyfunction]
fn verify_password(password: &str, password_hash: &str) -> PyResult<bool> {
    password::verify_password(password, password_hash).map_err(PyErr::from)
}

/// Generate a cryptographically secure random token
/// 
/// Args:
///     length (int): Number of bytes to generate (default: 32)
/// 
/// Returns:
///     str: URL-safe base64 encoded token
/// 
/// Raises:
///     RuntimeError: If token generation fails
/// 
/// Example:
///     >>> import irene_auth
///     >>> token = irene_auth.generate_token(32)
///     >>> len(token)
///     43
#[pyfunction]
#[pyo3(signature = (length=32))]
fn generate_token(length: usize) -> PyResult<String> {
    token::generate_token(length).map_err(PyErr::from)
}

/// Hash a token for secure database storage
/// 
/// Args:
///     token (str): The plaintext token to hash
/// 
/// Returns:
///     str: Hex-encoded SHA-256 hash
/// 
/// Example:
///     >>> import irene_auth
///     >>> token = irene_auth.generate_token()
///     >>> token_hash = irene_auth.hash_token(token)
///     >>> len(token_hash)
///     64
#[pyfunction]
fn hash_token(token: &str) -> PyResult<String> {
    token::hash_token(token).map_err(PyErr::from)
}

/// Verify a token against a stored hash (constant-time)
/// 
/// Args:
///     token (str): The plaintext token to verify
///     token_hash (str): The stored hash to compare against
/// 
/// Returns:
///     bool: True if token matches hash
/// 
/// Example:
///     >>> import irene_auth
///     >>> token = irene_auth.generate_token()
///     >>> token_hash = irene_auth.hash_token(token)
///     >>> irene_auth.verify_token_hash(token, token_hash)
///     True
#[pyfunction]
fn verify_token_hash(token: &str, token_hash: &str) -> PyResult<bool> {
    Ok(token::verify_token_hash(token, token_hash))
}

/// Encode a JWT token
/// 
/// Args:
///     payload (dict): Claims to include (must have 'sub' key for subject)
///     key (bytes): Secret key (HS256) or private key PEM (RS256)
///     exp_seconds (int): Expiration time in seconds from now
///     algorithm (str): Algorithm to use ("HS256" or "RS256")
/// 
/// Returns:
///     str: Encoded JWT string
/// 
/// Example:
///     >>> import irene_auth
///     >>> payload = {"sub": "user123", "role": "admin"}
///     >>> token = irene_auth.jwt_encode(payload, b"secret", 3600, "HS256")
///     >>> print(token[:20])
///     eyJ0eXAiOiJKV1QiLCJ...
#[pyfunction]
fn jwt_encode(
    _py: Python,
    payload: &Bound<'_, PyDict>,
    key: &[u8],
    exp_seconds: i64,
    algorithm: &str,
) -> PyResult<String> {
    // Extract subject
    let subject: String = payload
        .get_item("sub")?
        .ok_or_else(|| PyValueError::new_err("payload must contain 'sub' key"))?
        .extract()?;

    // Extract custom claims (everything except 'sub')
    let mut custom_claims = HashMap::new();
    for (k, v) in payload.iter() {
        let key_str: String = k.extract()?;
        if key_str != "sub" {
            let value: serde_json::Value = serde_json::from_str(&v.str()?.to_str()?)
                .unwrap_or_else(|_| serde_json::json!(v.str().unwrap().to_str().unwrap()));
            custom_claims.insert(key_str, value);
        }
    }

    jwt::encode_jwt(&subject, &custom_claims, key, exp_seconds, algorithm).map_err(PyErr::from)
}

/// Decode and validate a JWT token
/// 
/// Args:
///     token (str): JWT string to decode
///     key (bytes): Secret key (HS256) or public key PEM (RS256)
///     algorithms (list[str]): Allowed algorithms (e.g., ["HS256"])
/// 
/// Returns:
///     dict: Decoded claims including 'sub', 'exp', 'iat', and custom claims
/// 
/// Raises:
///     ValueError: If token is expired, invalid, or signature verification fails
/// 
/// Example:
///     >>> import irene_auth
///     >>> payload = {"sub": "user123", "role": "admin"}
///     >>> token = irene_auth.jwt_encode(payload, b"secret", 3600, "HS256")
///     >>> claims = irene_auth.jwt_decode(token, b"secret", ["HS256"])
///     >>> claims["sub"]
///     'user123'
#[pyfunction]
fn jwt_decode(
    py: Python,
    token: &str,
    key: &[u8],
    algorithms: Vec<String>,
) -> PyResult<Py<PyDict>> {
    let algorithms_refs: Vec<&str> = algorithms.iter().map(|s| s.as_str()).collect();
    let claims = jwt::decode_jwt(token, key, &algorithms_refs).map_err(PyErr::from)?;

    let dict = PyDict::new_bound(py);
    dict.set_item("sub", claims.sub)?;
    dict.set_item("exp", claims.exp)?;
    dict.set_item("iat", claims.iat)?;

    // Add custom claims
    for (k, v) in claims.custom {
        let py_value = pythonize::pythonize(py, &v)
            .unwrap_or_else(|_| py.None().into_bound(py));
        dict.set_item(k, py_value)?;
    }

    Ok(dict.unbind())
}

/// Irene Auth - Production-grade authentication library
/// 
/// Rust-powered cryptographic primitives for Python authentication workflows.
/// 
/// Features:
///     - Password hashing with Argon2id
///     - Secure token generation and hashing
///     - JWT encoding/decoding (HS256 + RS256)
///     - Constant-time comparisons
/// 
/// Security:
///     - Memory-hard password hashing (GPU resistant)
///     - Cryptographically secure random generation
///     - Constant-time token verification
///     - JWT expiration and validation
/// 
/// Example:
///     >>> import irene_auth
///     >>> 
///     >>> # Password hashing
///     >>> hash = irene_auth.hash_password("my_password")
///     >>> irene_auth.verify_password("my_password", hash)
///     True
///     >>> 
///     >>> # Token generation
///     >>> token = irene_auth.generate_token(32)
///     >>> token_hash = irene_auth.hash_token(token)
///     >>> irene_auth.verify_token_hash(token, token_hash)
///     True
///     >>> 
///     >>> # JWT
///     >>> payload = {"sub": "user123", "role": "admin"}
///     >>> jwt = irene_auth.jwt_encode(payload, b"secret", 3600, "HS256")
///     >>> claims = irene_auth.jwt_decode(jwt, b"secret", ["HS256"])
#[pymodule]
fn irene_auth(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(hash_password, m)?)?;
    m.add_function(wrap_pyfunction!(verify_password, m)?)?;
    m.add_function(wrap_pyfunction!(generate_token, m)?)?;
    m.add_function(wrap_pyfunction!(hash_token, m)?)?;
    m.add_function(wrap_pyfunction!(verify_token_hash, m)?)?;
    m.add_function(wrap_pyfunction!(jwt_encode, m)?)?;
    m.add_function(wrap_pyfunction!(jwt_decode, m)?)?;
    Ok(())
}
