use crate::addressing::Address;
use crate::database::entry::EntryValue;
use nonempty::NonEmpty;
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::str::FromStr;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attribute(pub String);

impl From<&str> for Attribute {
    fn from(str: &str) -> Self {
        Self(str.to_string())
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum QueryComponent<T>
where
    T: TryFrom<lexpr::Value>,
{
    Exact(T),
    In(Vec<T>),
    Contains(String),
    Variable(Option<String>),
}

#[derive(Debug, Clone, PartialEq)]
pub struct PatternQuery {
    pub entity: QueryComponent<Address>,
    pub attribute: QueryComponent<Attribute>,
    pub value: QueryComponent<EntryValue>,
}

impl TryFrom<lexpr::Value> for Address {
    type Error = QueryParseError;

    fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
        match value {
            lexpr::Value::Symbol(str) => {
                if let Some(address_str) = str.strip_prefix('@') {
                    address_str
                        .parse()
                        .map_err(|e: anyhow::Error| QueryParseError(e.to_string()))
                } else {
                    Err(QueryParseError(
                        "Incorrect address format (use @address).".into(),
                    ))
                }
            }
            _ => Err(QueryParseError(
                "Incorrect type for address (use @address).".into(),
            )),
        }
    }
}

impl TryFrom<lexpr::Value> for Attribute {
    type Error = QueryParseError;

    fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
        match value {
            lexpr::Value::String(str) => Ok(Attribute(str.to_string())),
            _ => Err(QueryParseError(
                "Can only convert to attribute from string.".into(),
            )),
        }
    }
}

impl TryFrom<lexpr::Value> for EntryValue {
    type Error = QueryParseError;

    fn try_from(value: lexpr::Value) -> Result<Self, Self::Error> {
        match value {
            lexpr::Value::Number(num) => {
                Ok(EntryValue::Number(num.as_f64().ok_or_else(|| {
                    QueryParseError(format!("Error processing number ({num:?})."))
                })?))
            }
            lexpr::Value::Char(chr) => Ok(EntryValue::String(chr.to_string())),
            lexpr::Value::String(str) => Ok(EntryValue::String(str.to_string())),
            lexpr::Value::Symbol(_) => Ok(EntryValue::Address(Address::try_from(value.clone())?)),
            _ => Err(QueryParseError(
                "Value can only be a string, number or address.".into(),
            )),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum QueryPart {
    Matches(PatternQuery),
    Type(String),
}

#[derive(Debug, Clone, PartialEq)]
pub enum QueryQualifier {
    And,
    Or,
    Not,
    Join,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MultiQuery {
    pub qualifier: QueryQualifier,
    pub queries: NonEmpty<Box<Query>>,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq)]
pub enum Query {
    SingleQuery(QueryPart),
    MultiQuery(MultiQuery),
}

#[derive(Debug, Clone)]
pub struct QueryParseError(String);

impl std::fmt::Display for QueryParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::error::Error for QueryParseError {}

impl TryFrom<&lexpr::Value> for Query {
    type Error = QueryParseError;

    fn try_from(expression: &lexpr::Value) -> Result<Self, Self::Error> {
        fn parse_component<T: TryFrom<lexpr::Value>>(
            value: &lexpr::Value,
        ) -> Result<QueryComponent<T>, QueryParseError>
        where
            QueryParseError: From<<T as TryFrom<lexpr::Value>>::Error>,
        {
            match value {
                lexpr::Value::Cons(cons) => {
                    if let lexpr::Value::Symbol(symbol) = cons.car() {
                        match symbol.borrow() {
                            "in" => {
                                let (cons_vec, _) = cons.clone().into_vec();
                                if let Some(split) = cons_vec.split_first() {
                                    let args = split.1;
                                    let values: Result<Vec<T>, _> = args
                                        .iter()
                                        .map(|value| T::try_from(value.clone()))
                                        .collect();

                                    Ok(QueryComponent::In(values?))
                                } else {
                                    Err(QueryParseError(
                                        "Malformed expression: Inner value cannot be empty.".into(),
                                    ))
                                }
                            }
                            "contains" => {
                                let (mut cons_vec, _) = cons.clone().into_vec();
                                match cons_vec.len() {
                                    2 => {
                                        let value = cons_vec.remove(1);
                                        if let lexpr::Value::String(str) = value {
                                            Ok(QueryComponent::Contains(str.into_string()))
                                        } else {
                                            Err(QueryParseError("Malformed expression: 'Contains' argument must be a string.".into()))
                                        }
                                    }
                                    _ => Err(QueryParseError(
                                    "Malformed expression: 'Contains' requires a single argument.".into()
                                )),
                                }
                            }
                            _ => Err(QueryParseError(format!(
                                "Malformed expression: Unknown symbol {}",
                                symbol
                            ))),
                        }
                    } else {
                        Err(QueryParseError(format!(
                            "Malformed expression: Inner value '{:?}' is not a symbol.",
                            value
                        )))
                    }
                }
                lexpr::Value::Symbol(symbol) if symbol.starts_with('?') => {
                    let var_name = symbol.strip_prefix('?').unwrap();
                    Ok(QueryComponent::Variable(if var_name.is_empty() {
                        None
                    } else {
                        Some(var_name.into())
                    }))
                }
                _ => Ok(QueryComponent::Exact(T::try_from(value.clone())?)),
            }
        }

        if let lexpr::Value::Cons(value) = expression {
            if let lexpr::Value::Symbol(symbol) = value.car() {
                match symbol.borrow() {
                    "matches" => {
                        let (cons_vec, _) = value.clone().into_vec();
                        if let [_, entity, attribute, value] = &cons_vec[..] {
                            let entity = parse_component::<Address>(entity)?;
                            let attribute = parse_component::<Attribute>(attribute)?;
                            let value = parse_component::<EntryValue>(value)?;
                            Ok(Query::SingleQuery(QueryPart::Matches(PatternQuery {
                                entity,
                                attribute,
                                value,
                            })))
                        } else {
                            Err(QueryParseError(
                                "Malformed expression: Wrong number of arguments to 'matches'."
                                    .into(),
                            ))
                        }
                    }
                    "type" => {
                        let (cons_vec, _) = value.clone().into_vec();
                        if let [_, type_name] = &cons_vec[..] {
                            if let lexpr::Value::String(type_name_str) = type_name {
                                Ok(Query::SingleQuery(QueryPart::Type(
                                    type_name_str.to_string(),
                                )))
                            } else {
                                Err(QueryParseError(
                                    "Malformed expression: Type must be specified as a string."
                                        .into(),
                                ))
                            }
                        } else {
                            Err(QueryParseError(
                                "Malformed expression: Wrong number of arguments to 'type'.".into(),
                            ))
                        }
                    }
                    "and" | "or" | "join" => {
                        let (cons_vec, _) = value.clone().into_vec();
                        let sub_expressions = &cons_vec[1..];
                        let values = sub_expressions
                            .iter()
                            .map(|value| Ok(Box::new(Query::try_from(value)?)))
                            .collect::<Result<Vec<Box<Query>>, QueryParseError>>()?;

                        if let Some(queries) = NonEmpty::from_vec(values) {
                            Ok(Query::MultiQuery(MultiQuery {
                                qualifier: match symbol.borrow() {
                                    "and" => QueryQualifier::And,
                                    "join" => QueryQualifier::Join,
                                    _ => QueryQualifier::Or,
                                },
                                queries,
                            }))
                        } else {
                            Err(QueryParseError(
                                "Malformed expression: sub-query list cannot be empty.".into(),
                            ))
                        }
                    }
                    "not" => {
                        let (cons_vec, _) = value.clone().into_vec();
                        let sub_expressions = &cons_vec[1..];
                        let values = sub_expressions
                            .iter()
                            .map(|value| Ok(Box::new(Query::try_from(value)?)))
                            .collect::<Result<Vec<Box<Query>>, QueryParseError>>()?;

                        if values.len() == 1 {
                            Ok(Query::MultiQuery(MultiQuery {
                                qualifier: QueryQualifier::Not,
                                queries: NonEmpty::from_vec(values).unwrap(),
                            }))
                        } else {
                            Err(QueryParseError(
                                "Malformed expression: NOT takes exactly one parameter.".into(),
                            ))
                        }
                    }
                    _ => Err(QueryParseError(format!(
                        "Malformed expression: Unknown symbol '{}'.",
                        symbol
                    ))),
                }
            } else {
                Err(QueryParseError(format!(
                    "Malformed expression: Value '{:?}' is not a symbol.",
                    value
                )))
            }
        } else {
            Err(QueryParseError("Malformed expression: Not a list.".into()))
        }
    }
}

impl FromStr for Query {
    type Err = QueryParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let sexp = lexpr::from_str_custom(s, lexpr::parse::Options::new())
            .map_err(|e| QueryParseError(format!("failed to parse s-expression: {e}")))?;
        Query::try_from(&sexp)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use anyhow::Result;
    use url::Url;

    #[test]
    fn test_matches() -> Result<()> {
        let query = "(matches ? ? ?)".parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::Variable(None)
            }))
        );

        let address = Address::Url(Url::parse("https://upend.dev").unwrap());
        let query = format!("(matches @{address} ? ?)").parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Exact(address),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::Variable(None)
            }))
        );

        let query = r#"(matches ? "FOO" ?)"#.parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Exact("FOO".into()),
                value: QueryComponent::Variable(None)
            }))
        );

        let value = EntryValue::Number(1337.93);
        let query = "(matches ? ? 1337.93)".parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::Exact(value)
            }))
        );

        Ok(())
    }

    #[test]
    fn test_joins() -> Result<()> {
        let query = "(matches ?a ?b ?)".parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(Some("a".into())),
                attribute: QueryComponent::Variable(Some("b".into())),
                value: QueryComponent::Variable(None)
            }))
        );

        Ok(())
    }

    #[test]
    fn test_in_parse() -> Result<()> {
        let query = r#"(matches ? (in "FOO" "BAR") ?)"#.parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::In(vec!("FOO".into(), "BAR".into())),
                value: QueryComponent::Variable(None)
            }))
        );

        let values: Vec<EntryValue> = vec!["FOO".into(), "BAR".into()];
        let query = r#"(matches ? ? (in "FOO" "BAR"))"#.parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::In(values)
            }))
        );

        let values = vec![EntryValue::Number(1337.93), EntryValue::Number(1968.12)];
        let query = r#"(matches ? ? (in 1337.93 1968.12))"#.parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::In(values)
            }))
        );

        // Invalid queries
        let values: Vec<EntryValue> = vec!["FOO".into(), EntryValue::Number(1337.93)];
        let query = r#"(matches ? ? (in "FOO" 1337.93))"#.parse::<Query>()?;

        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::In(values)
            }))
        );

        let values = vec![EntryValue::Number(1337.93), "FOO".into()];
        let query = r#"(matches ? ? (in 1337.93 "FOO"))"#.parse::<Query>()?;

        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::In(values)
            }))
        );

        Ok(())
    }

    #[test]
    fn test_contains() -> Result<()> {
        let query = r#"(matches (contains "foo") ? ?)"#.parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Contains("foo".to_string()),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::Variable(None)
            }))
        );

        let query = r#"(matches ? (contains "foo") ?)"#.parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Contains("foo".to_string()),
                value: QueryComponent::Variable(None),
            }))
        );

        let query = r#"(matches ? ? (contains "foo"))"#.parse::<Query>()?;
        assert_eq!(
            query,
            Query::SingleQuery(QueryPart::Matches(PatternQuery {
                entity: QueryComponent::Variable(None),
                attribute: QueryComponent::Variable(None),
                value: QueryComponent::Contains("foo".to_string())
            }))
        );

        Ok(())
    }
}
