use pyo3::create_exception;
use pyo3::exceptions::PyException;
use pyo3::exceptions::PyNotImplementedError;
use pyo3::prelude::*;
use pyo3::types::PyModule;
use qroissant_transport::TransportError;
use thiserror::Error;

create_exception!(
    qroissant,
    QroissantError,
    PyException,
    "Base exception for qroissant errors."
);
create_exception!(
    qroissant,
    DecodeError,
    QroissantError,
    "Raised when q IPC payload decoding fails."
);
create_exception!(
    qroissant,
    ProtocolError,
    QroissantError,
    "Raised when q IPC framing or protocol validation fails."
);
create_exception!(
    qroissant,
    TransportErrorPy,
    QroissantError,
    "Raised when transport IO or socket operations fail."
);
create_exception!(
    qroissant,
    OperationError,
    QroissantError,
    "Raised when an operation is unsupported in the current state."
);
create_exception!(
    qroissant,
    QRuntimeError,
    QroissantError,
    "Raised when the remote q process returns an error response."
);
create_exception!(
    qroissant,
    PoolError,
    QroissantError,
    "Raised when connection pool management fails."
);
create_exception!(
    qroissant,
    PoolClosedError,
    PoolError,
    "Raised when a closed pool is used."
);

#[derive(Debug, Error)]
pub enum PythonError {
    #[error("{0}")]
    Decode(String),
    #[error("{0}")]
    Protocol(String),
    #[error("{0}")]
    Transport(String),
    #[error("{0}")]
    Operation(String),
    #[error("{0}")]
    QRuntime(String),
    #[error("{0}")]
    Pool(String),
    #[error("connection pool is closed")]
    PoolClosed,
    #[error("{0}")]
    NotImplemented(String),
}

pub type PythonResult<T> = Result<T, PythonError>;

pub fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
    let py = module.py();
    module.add("QroissantError", py.get_type::<QroissantError>())?;
    module.add("DecodeError", py.get_type::<DecodeError>())?;
    module.add("ProtocolError", py.get_type::<ProtocolError>())?;
    module.add("TransportError", py.get_type::<TransportErrorPy>())?;
    module.add("OperationError", py.get_type::<OperationError>())?;
    module.add("QRuntimeError", py.get_type::<QRuntimeError>())?;
    module.add("PoolError", py.get_type::<PoolError>())?;
    module.add("PoolClosedError", py.get_type::<PoolClosedError>())?;
    Ok(())
}

pub fn to_py_err(error: PythonError) -> PyErr {
    match error {
        PythonError::Decode(message) => DecodeError::new_err(message),
        PythonError::Protocol(message) => ProtocolError::new_err(message),
        PythonError::Transport(message) => TransportErrorPy::new_err(message),
        PythonError::Operation(message) => OperationError::new_err(message),
        PythonError::QRuntime(message) => QRuntimeError::new_err(message),
        PythonError::Pool(message) => PoolError::new_err(message),
        PythonError::PoolClosed => PoolClosedError::new_err("connection pool is closed"),
        PythonError::NotImplemented(message) => PyNotImplementedError::new_err(message),
    }
}

pub fn map_transport_error(error: TransportError) -> PythonError {
    match error {
        TransportError::Closed => PythonError::Operation(error.to_string()),
        TransportError::Protocol(_) => PythonError::Protocol(error.to_string()),
        TransportError::Io(_)
        | TransportError::InvalidEndpoint(_)
        | TransportError::InvalidQueryLength(_) => PythonError::Transport(error.to_string()),
    }
}
