use smartstring::alias::String as SString;
use thiserror::Error as ThisError;

use crate::Key;

#[derive(Debug, ThisError)]
pub enum Error {
    #[error("Empty dataset")]
    EmptyData,
    #[error("Dataset size doesn't match expected: {0}, get: {1}")]
    DataSetSizeDoesntMatch(usize, usize),
    #[error("Dataset size doesn't match idx: {0}, size of data: {1}")]
    IndexOutOfRange(usize, usize),
    #[error("Indexes are out of order expected: {0:?}, get: {1:?}")]
    IndexOutOfOrder(Vec<Key>, Vec<Key>),
    #[error("Cannot find candidate: {0}")]
    Candidate(SString),
    #[error("Cannot find key: {0}")]
    NotFound(Key),
    #[error("Column already exists: {0}")]
    ColumnAlreadyExists(Key),
    #[error("Cannot broadcast data")]
    CannotBroadcast,
    #[error("Cannot find feature: {0}")]
    FeatureSearch(SString),
    #[error("Invalid operation for feature: {operation} with features: {features:?}")]
    InvalidOperation {
        operation: SString,
        features: Vec<SString>,
    },
    #[error("Unknown error: {0}")]
    UnknownError(String),
    #[error("IOError {0}")]
    IOError(#[from] std::io::Error),
    #[cfg(feature = "python")]
    #[error("PyO3 error: {0}")]
    PyO3(#[from] pyo3::prelude::PyErr),
    #[error("{0}")]
    Shape(#[from] ndarray::ShapeError),
    #[error("Cannot find feature: {0}")]
    MissingField(SString),
    #[cfg(feature = "polars-df")]
    #[error("Polars error {0}")]
    Polars(#[from] polars::error::PolarsError),

    #[error("Filter expression: {0}")]
    Filter(#[from] crate::filter::error::Error),
    #[error("Cast to type failed {0:?}")]
    CastFailed(Vec<(Key, String)>),
}

impl PartialEq for Error {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::EmptyData, Self::EmptyData) => true,
            (Self::NotFound(a), Self::NotFound(b)) => a == b,
            (Self::DataSetSizeDoesntMatch(a, b), Self::DataSetSizeDoesntMatch(c, d)) => {
                a == c && b == d
            }
            (Self::IndexOutOfRange(a, b), Self::IndexOutOfRange(c, d)) => a == c && b == d,
            (Self::IndexOutOfOrder(a, b), Self::IndexOutOfOrder(c, d)) => a == c && b == d,
            (Self::Candidate(a), Self::Candidate(b)) => a == b,
            (Self::FeatureSearch(a), Self::FeatureSearch(b)) => a == b,
            (Self::MissingField(a), Self::MissingField(b)) => a == b,
            (Self::UnknownError(a), Self::UnknownError(b)) => a == b,
            (
                Self::InvalidOperation {
                    operation: a,
                    features: b,
                },
                Self::InvalidOperation {
                    operation: c,
                    features: d,
                },
            ) => a == c && b == d,
            (Self::IOError(a), Self::IOError(b)) => a.kind() == b.kind(),
            (Self::ColumnAlreadyExists(a), Self::ColumnAlreadyExists(b)) => a == b,
            (Self::CannotBroadcast, Self::CannotBroadcast) => true,
            #[cfg(feature = "python")]
            (Self::PyO3(a), Self::PyO3(b)) => a.to_string() == b.to_string(),
            #[cfg(feature = "polars-df")]
            (Self::Polars(a), Self::Polars(b)) => a.to_string() == b.to_string(),
            _ => false,
        }
    }
}

#[cfg(test)]
mod test {
    #[cfg(feature = "python")]
    use pyo3::{exceptions::PyValueError, PyErr};

    use super::*;
    #[test]
    fn test_error() {
        let err1 = Error::EmptyData;
        assert_eq!(err1, Error::EmptyData);
        let err = Error::DataSetSizeDoesntMatch(1, 2);
        assert_eq!(err, Error::DataSetSizeDoesntMatch(1, 2));
        assert_ne!(err1, err);
        let err = Error::IndexOutOfRange(1, 2);
        assert_eq!(err, Error::IndexOutOfRange(1, 2));
        let err = Error::Candidate("test".into());
        assert_eq!(err, Error::Candidate("test".into()));
        let err = Error::FeatureSearch("test".into());
        assert_eq!(err, Error::FeatureSearch("test".into()));
        let err = Error::UnknownError("test".into());
        assert_eq!(err, Error::UnknownError("test".into()));
        let err = Error::MissingField("test".into());
        assert_eq!(err, Error::MissingField("test".into()));
        assert_eq!(
            Error::NotFound("test".into()),
            Error::NotFound("test".into())
        );
        assert_eq!(Error::CannotBroadcast, Error::CannotBroadcast);
        assert_eq!(
            Error::ColumnAlreadyExists("test".into()),
            Error::ColumnAlreadyExists("test".into())
        );
        assert_eq!(
            Error::IndexOutOfOrder(vec!["test".into()], vec!["test".into()]),
            Error::IndexOutOfOrder(vec!["test".into()], vec!["test".into()])
        );
        let err = Error::IOError(std::io::Error::new(std::io::ErrorKind::Other, "test"));
        assert_eq!(
            err,
            Error::IOError(std::io::Error::new(std::io::ErrorKind::Other, "test"))
        );
        let err = Error::InvalidOperation {
            operation: "test".into(),
            features: vec!["test".into()],
        };
        assert_eq!(
            err,
            Error::InvalidOperation {
                operation: "test".into(),
                features: vec!["test".into()],
            }
        );
        #[cfg(feature = "python")]
        {
            let err = Error::PyO3(PyErr::new::<PyValueError, _>("foo"));
            assert_eq!(err, Error::PyO3(PyErr::new::<PyValueError, _>("foo")));
        }
        #[cfg(feature = "polars-df")]
        {
            let err = Error::Polars(polars::error::PolarsError::ColumnNotFound("e".into()));
            assert_eq!(
                err,
                Error::Polars(polars::error::PolarsError::ColumnNotFound("e".into()))
            );
        }
    }
}
