//! Error types and exception mapping for Python bindings.
//!
//! Maps Rust errors from aifastdb-core to Python exceptions:
//!
//! | Rust Error Variant          | Python Exception         |
//! |-----------------------------|--------------------------|
//! | StoreNotFound               | AiFastDbError (KeyError) |
//! | RecordNotFound              | AiFastDbError (KeyError) |
//! | TypeMismatch                | TypeError                |
//! | PermissionDenied            | PermissionError          |
//! | IoError                     | OSError                  |
//! | Timeout                     | TimeoutError             |
//! | ConfigError / SerializError | ValueError               |
//! | Other                       | AiFastDbError            |

use pyo3::prelude::*;
use pyo3::exceptions::{
    PyTypeError, PyValueError,
    PyPermissionError, PyOSError, PyTimeoutError,
    PyRuntimeError,
};
use pyo3::create_exception;

// ---- Custom Exception Hierarchy ----

// Base exception: aifastdb.AiFastDbError (extends RuntimeError)
create_exception!(aifastdb, AiFastDbError, PyRuntimeError, "Base exception for all AiFastDb errors.");

// Specific exceptions
create_exception!(aifastdb, StoreNotFoundError, AiFastDbError, "Raised when a store is not found by alias.");
create_exception!(aifastdb, RecordNotFoundError, AiFastDbError, "Raised when a record is not found by ID.");
create_exception!(aifastdb, QueryError, AiFastDbError, "Raised on query execution failure.");
create_exception!(aifastdb, WriteError, AiFastDbError, "Raised on write operation failure.");
create_exception!(aifastdb, TransactionError, AiFastDbError, "Raised on transaction failure.");
create_exception!(aifastdb, StorageError, AiFastDbError, "Raised on storage/persistence failure.");
create_exception!(aifastdb, CircuitBreakerError, AiFastDbError, "Raised when a circuit breaker is open.");
create_exception!(aifastdb, RateLimitError, AiFastDbError, "Raised when rate limit is exceeded.");

/// Register exception types into the Python module.
pub fn register_exceptions(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add("AiFastDbError", m.py().get_type_bound::<AiFastDbError>())?;
    m.add("StoreNotFoundError", m.py().get_type_bound::<StoreNotFoundError>())?;
    m.add("RecordNotFoundError", m.py().get_type_bound::<RecordNotFoundError>())?;
    m.add("QueryError", m.py().get_type_bound::<QueryError>())?;
    m.add("WriteError", m.py().get_type_bound::<WriteError>())?;
    m.add("TransactionError", m.py().get_type_bound::<TransactionError>())?;
    m.add("StorageError", m.py().get_type_bound::<StorageError>())?;
    m.add("CircuitBreakerError", m.py().get_type_bound::<CircuitBreakerError>())?;
    m.add("RateLimitError", m.py().get_type_bound::<RateLimitError>())?;
    Ok(())
}

/// Convert a `FederationError` into the appropriate Python exception.
pub fn federation_error_to_pyerr(err: aifastdb_core::FederationError) -> PyErr {
    use aifastdb_core::FederationError as FE;

    match err {
        // Key-not-found family → specific custom exceptions
        FE::StoreNotFound(alias) => StoreNotFoundError::new_err(
            format!("Store not found: {}", alias)
        ),
        FE::RecordNotFound { id } => RecordNotFoundError::new_err(
            format!("Record not found: {}", id)
        ),

        // Type mismatch → Python TypeError
        FE::TypeMismatch { expected, got } => PyTypeError::new_err(
            format!("Store type mismatch: expected {:?}, got {:?}", expected, got)
        ),
        FE::DowncastFailed => PyTypeError::new_err("Failed to downcast store to specific type"),

        // Permission → Python PermissionError
        FE::PermissionDenied { caller, required } => PyPermissionError::new_err(
            format!("Permission denied: {} requires {}", caller, required)
        ),
        FE::ProtectionError { reason } => PyPermissionError::new_err(
            format!("Protection error: {}", reason)
        ),
        FE::NoOwnership { id } => PyPermissionError::new_err(
            format!("Record has no ownership metadata: {}", id)
        ),

        // IO → Python OSError
        FE::IoError(e) => PyOSError::new_err(format!("IO error: {}", e)),

        // Timeout → Python TimeoutError
        FE::Timeout(msg) => PyTimeoutError::new_err(format!("Timeout: {}", msg)),

        // Config / Serialization → Python ValueError
        FE::ConfigError(msg) => PyValueError::new_err(format!("Config error: {}", msg)),
        FE::SerializationError(msg) => PyValueError::new_err(format!("Serialization error: {}", msg)),
        FE::StoreAlreadyRegistered(alias) => PyValueError::new_err(
            format!("Store already registered: {}", alias)
        ),
        FE::UnsupportedOperation(msg) => PyValueError::new_err(
            format!("Unsupported operation: {}", msg)
        ),

        // Domain-specific errors → custom exceptions
        FE::QueryError(msg) => QueryError::new_err(format!("Query error: {}", msg)),
        FE::WriteError(msg) => WriteError::new_err(format!("Write error: {}", msg)),
        FE::TransactionError(msg) => TransactionError::new_err(
            format!("Transaction error: {}", msg)
        ),
        FE::CommitFailed { tx_id, db_alias, source } => TransactionError::new_err(
            format!("Commit failed for tx {} on {}: {}", tx_id, db_alias, source)
        ),
        FE::StorageError(msg) => StorageError::new_err(
            format!("Storage error: {}", msg)
        ),
        FE::IndexError(msg) => StorageError::new_err(
            format!("Index error: {}", msg)
        ),
        FE::LockError(msg) => StorageError::new_err(
            format!("Lock error: {}", msg)
        ),

        // Infrastructure errors → custom exceptions
        FE::CircuitBreakerOpen { alias } => CircuitBreakerError::new_err(
            format!("Circuit breaker open for store: {}", alias)
        ),
        FE::RateLimitExceeded => RateLimitError::new_err("Rate limit exceeded"),
        FE::StoreUnavailable(alias) => CircuitBreakerError::new_err(
            format!("Store unavailable: {}", alias)
        ),

        // Catch-all
        FE::Internal(msg) => AiFastDbError::new_err(format!("Internal error: {}", msg)),
    }
}
