use serde::{Deserialize, Serialize};

use crate::{operator::Operator, ColumnType};

use super::{ArrayIndexMode, SteVecMode, TokenFilter, Tokenizer};

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum IndexType {
    Ore,
    Ope,
    Match {
        tokenizer: Tokenizer,
        token_filters: Vec<TokenFilter>,
        k: usize,
        m: usize,
        /// Indicates if the original term should be added as a term
        /// which allows a match index to be used for equality
        include_original: bool,
    },
    /// This is called Unique because a Hash index must always
    /// have a unique constraint on it but it is technically a Determinstic
    /// index. Naming is hard.
    Unique {
        #[serde(default)]
        token_filters: Vec<TokenFilter>,
    },
    SteVec {
        prefix: String,
        #[serde(default)]
        // Named term_filters because we don't do any tokenization on STE Vec terms
        term_filters: Vec<TokenFilter>,
        #[serde(default)]
        array_index_mode: ArrayIndexMode,
        #[serde(default)]
        mode: SteVecMode,
    },
}

impl IndexType {
    /// Returns true or false based on the below:
    ///
    /// Non string type indexes support:
    ///
    /// Ore: Eq | Lt | Lte | Gt | Gte,
    /// Unique: Eq
    ///
    /// String type indexes support:
    ///
    /// Match: Like | ILike
    /// Ore: Lt | Lte | Gt | Gte,
    /// Unique: Eq
    ///
    pub fn supports(&self, op: &Operator, cast_type: &ColumnType) -> bool {
        use Operator::*;
        // returns a bool

        match (self, op, cast_type) {
            (Self::Ore, Eq, ColumnType::Text) => false,
            (Self::Ore, Eq | Lt | Lte | Gt | Gte, _) => true,
            (Self::Ore, _, _) => false,

            (Self::Ope, Eq, ColumnType::Text) => false,
            (Self::Ope, Eq | Lt | Lte | Gt | Gte, _) => true,
            (Self::Ope, _, _) => false,

            (Self::Match { .. }, Like | ILike, ColumnType::Text) => true,
            (Self::Match { .. }, _, _) => false,

            (Self::Unique { .. }, Eq, _) => true,
            (Self::Unique { .. }, _, _) => false,

            (Self::SteVec { .. }, Operator::STEVecOperator(_), ColumnType::Json) => true,
            (Self::SteVec { .. }, _, _) => false,
        }
    }

    pub fn supported_operations(&self, cast_type: &ColumnType) -> Vec<Operator> {
        use Operator::*;

        match self {
            IndexType::Ore => match cast_type {
                ColumnType::Text => vec![Lt, Lte, Gt, Gte],
                _ => vec![Eq, Lt, Lte, Gt, Gte],
            },
            IndexType::Ope => match cast_type {
                ColumnType::Text => vec![Lt, Lte, Gt, Gte],
                _ => vec![Eq, Lt, Lte, Gt, Gte],
            },
            IndexType::Match { .. } => vec![Like],
            IndexType::Unique { .. } => vec![Eq],
            IndexType::SteVec { .. } => vec![
                STEVecOperator(crate::operator::STEVecOperator::RightContainsLeft),
                STEVecOperator(crate::operator::STEVecOperator::LeftContainsRight),
            ],
        }
    }

    pub fn is_orderable(&self) -> bool {
        matches!(self, Self::Ore | Self::Ope)
    }

    /// String identifier for the index type.
    /// Useful when naming columns
    pub fn as_str(&self) -> &str {
        match self {
            Self::Ore => "ore",
            Self::Ope => "ope",
            Self::Match { .. } => "match",
            Self::Unique { .. } => "unique",
            Self::SteVec { .. } => "ste-vec",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::column::Index;

    fn non_string_types() -> Vec<ColumnType> {
        vec![
            ColumnType::SmallInt,
            ColumnType::BigInt,
            ColumnType::Int,
            ColumnType::Boolean,
            ColumnType::Date,
            ColumnType::Decimal,
            ColumnType::Float,
            ColumnType::Timestamp,
        ]
    }

    fn string_type() -> ColumnType {
        ColumnType::Text
    }

    #[test]
    fn test_operator_support_ore() {
        let index_type = Index::new_ore();

        // Non String types
        non_string_types().into_iter().for_each(|t| {
            // Supports range and eq ops for non string types
            assert!(index_type.supports(&Operator::Eq, &t));
            assert!(index_type.supports(&Operator::Lt, &t));
            assert!(index_type.supports(&Operator::Lte, &t));
            assert!(index_type.supports(&Operator::Gt, &t));
            assert!(index_type.supports(&Operator::Gte, &t));

            // Does not support
            assert!(!IndexType::Ore.supports(&Operator::Like, &t));
            assert!(!IndexType::Ore.supports(&Operator::ILike, &t));
        });

        // Supports range for string types
        assert!(index_type.supports(&Operator::Lt, &string_type()));
        assert!(index_type.supports(&Operator::Lte, &string_type()));
        assert!(index_type.supports(&Operator::Gt, &string_type()));
        assert!(index_type.supports(&Operator::Gte, &string_type()));

        // Does not support for string types
        assert!(!index_type.supports(&Operator::Eq, &string_type()));
        assert!(!IndexType::Ore.supports(&Operator::Like, &string_type()));
        assert!(!IndexType::Ore.supports(&Operator::ILike, &string_type()));
    }

    #[test]
    fn test_operator_support_ope() {
        let index_type = Index::new_ope();

        // Non String types
        non_string_types().into_iter().for_each(|t| {
            // Supports range and eq ops for non string types
            assert!(index_type.supports(&Operator::Eq, &t));
            assert!(index_type.supports(&Operator::Lt, &t));
            assert!(index_type.supports(&Operator::Lte, &t));
            assert!(index_type.supports(&Operator::Gt, &t));
            assert!(index_type.supports(&Operator::Gte, &t));

            // Does not support
            assert!(!IndexType::Ope.supports(&Operator::Like, &t));
            assert!(!IndexType::Ope.supports(&Operator::ILike, &t));
        });

        // Supports range for string types
        assert!(index_type.supports(&Operator::Lt, &string_type()));
        assert!(index_type.supports(&Operator::Lte, &string_type()));
        assert!(index_type.supports(&Operator::Gt, &string_type()));
        assert!(index_type.supports(&Operator::Gte, &string_type()));

        // Does not support for string types
        assert!(!index_type.supports(&Operator::Eq, &string_type()));
        assert!(!IndexType::Ope.supports(&Operator::Like, &string_type()));
        assert!(!IndexType::Ope.supports(&Operator::ILike, &string_type()));
    }

    #[test]
    fn test_operator_support_match() {
        let index_type = Index::new_match();

        // Supports Like on string type
        assert!(index_type.supports(&Operator::Like, &string_type()));
        assert!(index_type.supports(&Operator::ILike, &string_type()));

        // Doesn't support on string types
        assert!(!index_type.supports(&Operator::Eq, &string_type()));
        assert!(!index_type.supports(&Operator::Lt, &string_type()));
        assert!(!index_type.supports(&Operator::Lte, &string_type()));
        assert!(!index_type.supports(&Operator::Gt, &string_type()));
        assert!(!index_type.supports(&Operator::Gte, &string_type()));

        // Doesn't support on non string types
        non_string_types().into_iter().for_each(|t| {
            assert!(!index_type.supports(&Operator::Like, &t));
            assert!(!index_type.supports(&Operator::ILike, &t));
            assert!(!index_type.supports(&Operator::Eq, &t));
            assert!(!index_type.supports(&Operator::Lt, &t));
            assert!(!index_type.supports(&Operator::Lte, &t));
            assert!(!index_type.supports(&Operator::Gt, &t));
            assert!(!index_type.supports(&Operator::Gte, &t));
        });
    }

    #[test]
    fn test_operator_support_unique() {
        let index_type = Index::new_unique();

        // Supports Eq on all types
        assert!(index_type.supports(&Operator::Eq, &string_type()));

        non_string_types().into_iter().for_each(|t| {
            assert!(index_type.supports(&Operator::Eq, &t));
        });

        // Doesn't support
        non_string_types().into_iter().for_each(|t| {
            assert!(!index_type.supports(&Operator::Like, &t));
            assert!(!index_type.supports(&Operator::ILike, &t));
            assert!(!index_type.supports(&Operator::Lt, &t));
            assert!(!index_type.supports(&Operator::Lte, &t));
            assert!(!index_type.supports(&Operator::Gt, &t));
            assert!(!index_type.supports(&Operator::Gte, &t));
        });

        assert!(!index_type.supports(&Operator::Like, &string_type()));
        assert!(!index_type.supports(&Operator::ILike, &string_type()));
        assert!(!index_type.supports(&Operator::Lt, &string_type()));
        assert!(!index_type.supports(&Operator::Lte, &string_type()));
        assert!(!index_type.supports(&Operator::Gt, &string_type()));
        assert!(!index_type.supports(&Operator::Gte, &string_type()));
    }

    #[test]
    fn test_is_orderable() {
        assert!(!Index::new_match().is_orderable());
        assert!(!Index::new_unique().is_orderable());
        assert!(Index::new_ore().is_orderable());
        assert!(Index::new_ope().is_orderable());
    }

    #[test]
    fn test_ste_vec_default_array_index_mode() {
        use super::super::ArrayIndexMode;

        let index = IndexType::SteVec {
            prefix: "test".to_string(),
            term_filters: vec![],
            array_index_mode: ArrayIndexMode::default(),
            mode: SteVecMode::default(),
        };

        if let IndexType::SteVec {
            array_index_mode, ..
        } = index
        {
            assert_eq!(array_index_mode, ArrayIndexMode::NONE);
        } else {
            panic!("Expected SteVec");
        }
    }

    #[test]
    fn test_ste_vec_serialization_with_array_index_mode() {
        use super::super::ArrayIndexMode;

        let index = IndexType::SteVec {
            prefix: "test".to_string(),
            term_filters: vec![],
            array_index_mode: ArrayIndexMode::WILDCARD,
            mode: SteVecMode::default(),
        };

        let json = serde_json::to_string(&index).unwrap();
        assert!(json.contains("\"wildcard\":true"));
    }

    #[test]
    fn test_ope_serialization() {
        // Wire-format kind tag — `IndexType::Ope` must serialize as
        // `{"kind": "ope"}` and round-trip back to the same variant.
        let index = IndexType::Ope;

        let serialized = serde_json::to_value(&index).unwrap();
        assert_eq!(serialized, serde_json::json!({"kind": "ope"}));

        let parsed: IndexType = serde_json::from_value(serialized).unwrap();
        assert_eq!(parsed, IndexType::Ope);
    }

    #[test]
    fn test_ste_vec_deserializes_without_mode_field_as_standard() {
        use super::super::SteVecMode;

        let json = serde_json::json!({
            "kind": "ste-vec",
            "prefix": "p",
            "term_filters": [],
            "array_index_mode": "all"
        });
        let parsed: IndexType = serde_json::from_value(json).unwrap();

        if let IndexType::SteVec { mode, .. } = parsed {
            assert_eq!(mode, SteVecMode::Standard);
        } else {
            panic!("expected SteVec");
        }
    }

    #[test]
    fn test_ste_vec_round_trips_explicit_standard_mode() {
        use super::super::SteVecMode;

        let original = IndexType::SteVec {
            prefix: "p".into(),
            term_filters: vec![],
            array_index_mode: super::super::ArrayIndexMode::default(),
            mode: SteVecMode::Standard,
        };
        let serialized = serde_json::to_value(&original).unwrap();
        assert!(
            serialized.get("mode") == Some(&serde_json::json!("standard")),
            "expected mode=standard on the wire, got {serialized}"
        );

        let parsed: IndexType = serde_json::from_value(serialized).unwrap();
        assert_eq!(parsed, original);
    }
}
