use crate::{EntityDescriptor, SelectQuery, Value};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
    Eq,
    Ne,
    Gt,
    Gte,
    Lt,
    Lte,
    Like,
    NotLike,
    In,
    NotIn,
    InLarge,
    NotInLarge,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExprFunction {
    Soundex,
    Gbk,
    Count,
    Sum,
    Avg,
    Min,
    Max,
    Stddev,
    StddevPop,
    VarSamp,
    VarPop,
    BitAnd,
    BitOr,
    BitXor,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    Column(String),
    Value(Value),
    Function {
        function: ExprFunction,
        args: Vec<Expr>,
    },
    Binary {
        left: Box<Expr>,
        op: BinaryOp,
        right: Box<Expr>,
    },
    SubQuery {
        left: Box<Expr>,
        op: BinaryOp,
        entity: EntityDescriptor,
        query: Box<SelectQuery>,
    },
    Between {
        expr: Box<Expr>,
        lower: Box<Expr>,
        upper: Box<Expr>,
    },
    IsNull(Box<Expr>),
    IsNotNull(Box<Expr>),
    And(Vec<Expr>),
    Or(Vec<Expr>),
    Not(Box<Expr>),
}

impl Expr {
    pub fn column(name: impl Into<String>) -> Self {
        Self::Column(name.into())
    }

    pub fn value(value: impl Into<Value>) -> Self {
        Self::Value(value.into())
    }

    pub fn function(function: ExprFunction, args: impl IntoIterator<Item = Expr>) -> Self {
        Self::Function {
            function,
            args: args.into_iter().collect(),
        }
    }

    pub fn soundex(expr: Expr) -> Self {
        Self::function(ExprFunction::Soundex, [expr])
    }

    pub fn gbk(expr: Expr) -> Self {
        Self::function(ExprFunction::Gbk, [expr])
    }

    pub fn count_all() -> Self {
        Self::function(ExprFunction::Count, [])
    }

    pub fn count_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::Count, [expr])
    }

    pub fn sum_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::Sum, [expr])
    }

    pub fn avg_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::Avg, [expr])
    }

    pub fn min_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::Min, [expr])
    }

    pub fn max_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::Max, [expr])
    }

    pub fn stddev_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::Stddev, [expr])
    }

    pub fn stddev_pop_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::StddevPop, [expr])
    }

    pub fn var_samp_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::VarSamp, [expr])
    }

    pub fn var_pop_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::VarPop, [expr])
    }

    pub fn bit_and_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::BitAnd, [expr])
    }

    pub fn bit_or_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::BitOr, [expr])
    }

    pub fn bit_xor_expr(expr: Expr) -> Self {
        Self::function(ExprFunction::BitXor, [expr])
    }

    pub fn sound_like(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::binary(
            Self::soundex(Self::column(column)),
            BinaryOp::Eq,
            Self::soundex(Self::value(value)),
        )
    }

    pub fn eq(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::binary(Self::column(column), BinaryOp::Eq, Self::value(value))
    }

    pub fn ne(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::binary(Self::column(column), BinaryOp::Ne, Self::value(value))
    }

    pub fn gt(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::binary(Self::column(column), BinaryOp::Gt, Self::value(value))
    }

    pub fn gte(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::binary(Self::column(column), BinaryOp::Gte, Self::value(value))
    }

    pub fn lt(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::binary(Self::column(column), BinaryOp::Lt, Self::value(value))
    }

    pub fn lte(column: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::binary(Self::column(column), BinaryOp::Lte, Self::value(value))
    }

    pub fn like(column: impl Into<String>, pattern: impl Into<String>) -> Self {
        Self::binary(
            Self::column(column),
            BinaryOp::Like,
            Self::value(pattern.into()),
        )
    }

    pub fn not_like(column: impl Into<String>, pattern: impl Into<String>) -> Self {
        Self::binary(
            Self::column(column),
            BinaryOp::NotLike,
            Self::value(pattern.into()),
        )
    }

    pub fn contain(column: impl Into<String>, value: impl Into<String>) -> Self {
        Self::like(column, format!("%{}%", value.into()))
    }

    pub fn not_contain(column: impl Into<String>, value: impl Into<String>) -> Self {
        Self::not_like(column, format!("%{}%", value.into()))
    }

    pub fn begin_with(column: impl Into<String>, value: impl Into<String>) -> Self {
        Self::like(column, format!("{}%", value.into()))
    }

    pub fn not_begin_with(column: impl Into<String>, value: impl Into<String>) -> Self {
        Self::not_like(column, format!("{}%", value.into()))
    }

    pub fn end_with(column: impl Into<String>, value: impl Into<String>) -> Self {
        Self::like(column, format!("%{}", value.into()))
    }

    pub fn not_end_with(column: impl Into<String>, value: impl Into<String>) -> Self {
        Self::not_like(column, format!("%{}", value.into()))
    }

    pub fn binary(left: Expr, op: BinaryOp, right: Expr) -> Self {
        Self::Binary {
            left: Box::new(left),
            op,
            right: Box::new(right),
        }
    }

    pub fn compare_columns(
        left_column: impl Into<String>,
        op: BinaryOp,
        right_column: impl Into<String>,
    ) -> Self {
        Self::binary(Self::column(left_column), op, Self::column(right_column))
    }

    pub fn in_list(column: impl Into<String>, values: impl IntoIterator<Item = Value>) -> Self {
        Self::binary(
            Self::column(column),
            BinaryOp::In,
            Self::Value(Value::List(values.into_iter().collect())),
        )
    }

    pub fn not_in_list(column: impl Into<String>, values: impl IntoIterator<Item = Value>) -> Self {
        Self::binary(
            Self::column(column),
            BinaryOp::NotIn,
            Self::Value(Value::List(values.into_iter().collect())),
        )
    }

    pub fn in_large(column: impl Into<String>, values: impl IntoIterator<Item = Value>) -> Self {
        Self::binary(
            Self::column(column),
            BinaryOp::InLarge,
            Self::Value(Value::List(values.into_iter().collect())),
        )
    }

    pub fn not_in_large(
        column: impl Into<String>,
        values: impl IntoIterator<Item = Value>,
    ) -> Self {
        Self::binary(
            Self::column(column),
            BinaryOp::NotInLarge,
            Self::Value(Value::List(values.into_iter().collect())),
        )
    }

    pub fn in_subquery(
        column: impl Into<String>,
        entity: EntityDescriptor,
        query: SelectQuery,
        field: impl Into<String>,
    ) -> Self {
        Self::subquery(Self::column(column), BinaryOp::In, entity, query, field)
    }

    pub fn not_in_subquery(
        column: impl Into<String>,
        entity: EntityDescriptor,
        query: SelectQuery,
        field: impl Into<String>,
    ) -> Self {
        Self::subquery(Self::column(column), BinaryOp::NotIn, entity, query, field)
    }

    pub fn subquery(
        left: Expr,
        op: BinaryOp,
        entity: EntityDescriptor,
        mut query: SelectQuery,
        field: impl Into<String>,
    ) -> Self {
        query.projection = vec![field.into()];
        Self::SubQuery {
            left: Box::new(left),
            op,
            entity,
            query: Box::new(query),
        }
    }

    pub fn between(
        column: impl Into<String>,
        lower: impl Into<Value>,
        upper: impl Into<Value>,
    ) -> Self {
        Self::Between {
            expr: Box::new(Self::column(column)),
            lower: Box::new(Self::value(lower)),
            upper: Box::new(Self::value(upper)),
        }
    }

    pub fn is_null(column: impl Into<String>) -> Self {
        Self::IsNull(Box::new(Self::column(column)))
    }

    pub fn is_not_null(column: impl Into<String>) -> Self {
        Self::IsNotNull(Box::new(Self::column(column)))
    }

    pub fn and(parts: impl IntoIterator<Item = Expr>) -> Self {
        Self::And(parts.into_iter().collect())
    }

    pub fn or(parts: impl IntoIterator<Item = Expr>) -> Self {
        Self::Or(parts.into_iter().collect())
    }

    pub fn negate(expr: Expr) -> Self {
        Self::Not(Box::new(expr))
    }

    pub fn and_expr(self, other: Expr) -> Self {
        match self {
            Self::And(mut parts) => {
                parts.push(other);
                Self::And(parts)
            }
            expr => Self::And(vec![expr, other]),
        }
    }

    pub fn or_expr(self, other: Expr) -> Self {
        match self {
            Self::Or(mut parts) => {
                parts.push(other);
                Self::Or(parts)
            }
            expr => Self::Or(vec![expr, other]),
        }
    }
}
