from __future__ import annotations

from .._compat import optional_import
from .base import ErrorInfo, default_message

PandasErrors = optional_import("pandas", "errors")
NumpyExceptions = optional_import("numpy", "exceptions")
NumpyLinalg = optional_import("numpy.linalg")
PyArrow = optional_import("pyarrow")
PolarsExceptions = optional_import("polars", "exceptions")
SciPyLinalg = optional_import("scipy.linalg")


def _class(name: str, module: object) -> type[object] | None:
    return getattr(module, name, None) if module else None


def describe_data_tool_exception(exc: Exception) -> ErrorInfo | None:
    if PandasErrors:
        data_types = tuple(
            filter(
                None,
                (
                    _class("ParserError", PandasErrors),
                    _class("EmptyDataError", PandasErrors),
                    _class("MergeError", PandasErrors),
                    _class("IndexingError", PandasErrors),
                    _class("InvalidIndexError", PandasErrors),
                    _class("OutOfBoundsDatetime", PandasErrors),
                    _class("OutOfBoundsTimedelta", PandasErrors),
                    _class("IntCastingNaNError", PandasErrors),
                    _class("UndefinedVariableError", PandasErrors),
                    _class("DataError", PandasErrors),
                    _class("DuplicateLabelError", PandasErrors),
                    _class("InvalidComparison", PandasErrors),
                    _class("SpecificationError", PandasErrors),
                    _class("UnsupportedFunctionCall", PandasErrors),
                    _class("UnsortedIndexError", PandasErrors),
                    _class("NullFrequencyError", PandasErrors),
                ),
            )
        )
        if data_types and isinstance(exc, data_types):
            return ErrorInfo(422, "invalid_data", default_message(422), error_type=exc.__class__.__name__, retryable=False)
        database_error = _class("DatabaseError", PandasErrors)
        if database_error and isinstance(exc, database_error):
            return ErrorInfo(503, "data_backend_unavailable", default_message(503), error_type=exc.__class__.__name__, retryable=True)

    if NumpyExceptions:
        if isinstance(exc, tuple(filter(None, (_class("AxisError", NumpyExceptions), _class("DTypePromotionError", NumpyExceptions), _class("TooHardError", NumpyExceptions))))):
            return ErrorInfo(422, "invalid_data", default_message(422), error_type=exc.__class__.__name__, retryable=False)

    if NumpyLinalg and isinstance(exc, tuple(filter(None, (getattr(NumpyLinalg, "LinAlgError", None),)))):
        return ErrorInfo(422, "invalid_data", default_message(422), error_type=exc.__class__.__name__, retryable=False)

    if PyArrow:
        arrow_types = tuple(
            filter(
                None,
                (
                    getattr(PyArrow, "ArrowInvalid", None),
                    getattr(PyArrow, "ArrowTypeError", None),
                    getattr(PyArrow, "ArrowIndexError", None),
                    getattr(PyArrow, "ArrowKeyError", None),
                    getattr(PyArrow, "ArrowSerializationError", None),
                ),
            )
        )
        if arrow_types and isinstance(exc, arrow_types):
            return ErrorInfo(422, "invalid_data", default_message(422), error_type=exc.__class__.__name__, retryable=False)
        backend_types = tuple(filter(None, (getattr(PyArrow, "ArrowIOError", None), getattr(PyArrow, "ArrowMemoryError", None), getattr(PyArrow, "ArrowCancelled", None), getattr(PyArrow, "ArrowCapacityError", None))))
        if backend_types and isinstance(exc, backend_types):
            return ErrorInfo(503, "data_backend_unavailable", default_message(503), error_type=exc.__class__.__name__, retryable=True)

    if PolarsExceptions:
        invalid_types = tuple(
            filter(
                None,
                (
                    _class("ColumnNotFoundError", PolarsExceptions),
                    _class("InvalidOperationError", PolarsExceptions),
                    _class("NoDataError", PolarsExceptions),
                    _class("OutOfBoundsError", PolarsExceptions),
                    _class("RowsError", PolarsExceptions),
                    _class("SchemaError", PolarsExceptions),
                    _class("SchemaFieldNotFoundError", PolarsExceptions),
                    _class("ShapeError", PolarsExceptions),
                    _class("StructFieldNotFoundError", PolarsExceptions),
                    _class("SQLSyntaxError", PolarsExceptions),
                    _class("SQLInterfaceError", PolarsExceptions),
                ),
            )
        )
        if invalid_types and isinstance(exc, invalid_types):
            return ErrorInfo(422, "invalid_data", default_message(422), error_type=exc.__class__.__name__, retryable=False)
        conflict_types = tuple(filter(None, (_class("DuplicateError", PolarsExceptions), _class("ParameterCollisionError", PolarsExceptions))))
        if conflict_types and isinstance(exc, conflict_types):
            return ErrorInfo(409, "conflict", default_message(409), error_type=exc.__class__.__name__, retryable=False)
        panic_type = _class("PanicException", PolarsExceptions)
        if panic_type and isinstance(exc, panic_type):
            return ErrorInfo(500, "data_runtime_error", default_message(500), error_type=exc.__class__.__name__)

    if SciPyLinalg and isinstance(exc, tuple(filter(None, (getattr(SciPyLinalg, "LinAlgError", None),)))):
        return ErrorInfo(422, "invalid_data", default_message(422), error_type=exc.__class__.__name__, retryable=False)

    return None
