use super::dml::lower_select;
use crate::ast as executor_ast;
use crate::error::DbError;
use crate::parser::ast;

pub fn lower_expr(parser_expr: ast::Expr) -> Result<executor_ast::expressions::Expr, DbError> {
    match parser_expr {
        ast::Expr::Identifier(id) => Ok(executor_ast::expressions::Expr::Identifier(id)),
        ast::Expr::Variable(id) => Ok(executor_ast::expressions::Expr::Identifier(id)),
        ast::Expr::QualifiedIdentifier(parts) => {
            Ok(executor_ast::expressions::Expr::QualifiedIdentifier(parts))
        }
        ast::Expr::Wildcard => Ok(executor_ast::expressions::Expr::Wildcard),
        ast::Expr::QualifiedWildcard(parts) => {
            Ok(executor_ast::expressions::Expr::QualifiedWildcard(parts))
        }
        ast::Expr::Integer(i) => Ok(executor_ast::expressions::Expr::Integer(i)),
        ast::Expr::Float(f) => Ok(executor_ast::expressions::Expr::FloatLiteral(f)),
        ast::Expr::String(s) => Ok(executor_ast::expressions::Expr::String(s)),
        ast::Expr::UnicodeString(s) => Ok(executor_ast::expressions::Expr::UnicodeString(s)),
        ast::Expr::BinaryLiteral(b) => Ok(executor_ast::expressions::Expr::BinaryLiteral(b)),
        ast::Expr::Null => Ok(executor_ast::expressions::Expr::Null),
        ast::Expr::Bool(b) => Ok(executor_ast::expressions::Expr::Integer(if b {
            1
        } else {
            0
        })),
        ast::Expr::Binary { left, op, right } => match op {
            ast::BinaryOp::Like => Ok(executor_ast::expressions::Expr::Like {
                expr: Box::new(lower_expr(*left)?),
                pattern: Box::new(lower_expr(*right)?),
                negated: false,
            }),
            _ => Ok(executor_ast::expressions::Expr::Binary {
                left: Box::new(lower_expr(*left)?),
                op: lower_binary_op(op)?,
                right: Box::new(lower_expr(*right)?),
            }),
        },
        ast::Expr::Unary { op, expr } => Ok(executor_ast::expressions::Expr::Unary {
            op: lower_unary_op(op),
            expr: Box::new(lower_expr(*expr)?),
        }),
        ast::Expr::IsNull(expr) => Ok(executor_ast::expressions::Expr::IsNull(Box::new(
            lower_expr(*expr)?,
        ))),
        ast::Expr::IsNotNull(expr) => Ok(executor_ast::expressions::Expr::IsNotNull(Box::new(
            lower_expr(*expr)?,
        ))),
        ast::Expr::Cast { expr, target } => Ok(executor_ast::expressions::Expr::Cast {
            expr: Box::new(lower_expr(*expr)?),
            target: lower_data_type(target)?,
        }),
        ast::Expr::TryCast { expr, target } => Ok(executor_ast::expressions::Expr::TryCast {
            expr: Box::new(lower_expr(*expr)?),
            target: lower_data_type(target)?,
        }),
        ast::Expr::Convert {
            target,
            expr,
            style,
        } => Ok(executor_ast::expressions::Expr::Convert {
            target: lower_data_type(target)?,
            expr: Box::new(lower_expr(*expr)?),
            style,
        }),
        ast::Expr::TryConvert {
            target,
            expr,
            style,
        } => Ok(executor_ast::expressions::Expr::TryConvert {
            target: lower_data_type(target)?,
            expr: Box::new(lower_expr(*expr)?),
            style,
        }),
        ast::Expr::Case {
            operand,
            when_clauses,
            else_result,
        } => Ok(executor_ast::expressions::Expr::Case {
            operand: operand.map(|e| lower_expr(*e)).transpose()?.map(Box::new),
            when_clauses: when_clauses
                .into_iter()
                .map(|w| {
                    Ok(executor_ast::expressions::WhenClause {
                        condition: lower_expr(w.condition)?,
                        result: lower_expr(w.result)?,
                    })
                })
                .collect::<Result<Vec<_>, _>>()?,
            else_result: else_result
                .map(|e| lower_expr(*e))
                .transpose()?
                .map(Box::new),
        }),
        ast::Expr::InList {
            expr,
            list,
            negated,
        } => Ok(executor_ast::expressions::Expr::InList {
            expr: Box::new(lower_expr(*expr)?),
            list: list
                .into_iter()
                .map(lower_expr)
                .collect::<Result<Vec<_>, _>>()?,
            negated,
        }),
        ast::Expr::InSubquery {
            expr,
            subquery,
            negated,
        } => Ok(executor_ast::expressions::Expr::InSubquery {
            expr: Box::new(lower_expr(*expr)?),
            subquery: Box::new(lower_select(*subquery)?),
            negated,
        }),
        ast::Expr::Between {
            expr,
            low,
            high,
            negated,
        } => Ok(executor_ast::expressions::Expr::Between {
            expr: Box::new(lower_expr(*expr)?),
            low: Box::new(lower_expr(*low)?),
            high: Box::new(lower_expr(*high)?),
            negated,
        }),
        ast::Expr::Like {
            expr,
            pattern,
            negated,
        } => Ok(executor_ast::expressions::Expr::Like {
            expr: Box::new(lower_expr(*expr)?),
            pattern: Box::new(lower_expr(*pattern)?),
            negated,
        }),
        ast::Expr::Exists { subquery, negated } => Ok(executor_ast::expressions::Expr::Exists {
            subquery: Box::new(lower_select(*subquery)?),
            negated,
        }),
        ast::Expr::Subquery(s) => Ok(executor_ast::expressions::Expr::Subquery(Box::new(
            lower_select(*s)?,
        ))),
        ast::Expr::FunctionCall {
            name,
            args,
            within_group,
        } => Ok(executor_ast::expressions::Expr::FunctionCall {
            name,
            args: args
                .into_iter()
                .map(lower_expr)
                .collect::<Result<Vec<_>, _>>()?,
            within_group: within_group
                .into_iter()
                .map(lower_order_by_expr)
                .collect::<Result<Vec<_>, _>>()?,
        }),
        ast::Expr::WindowFunction {
            name,
            args,
            partition_by,
            order_by,
            frame,
        } => Ok(executor_ast::expressions::Expr::WindowFunction {
            func: lower_window_func(name.as_str()),
            args: args
                .into_iter()
                .map(lower_expr)
                .collect::<Result<Vec<_>, _>>()?,
            partition_by: partition_by
                .into_iter()
                .map(lower_expr)
                .collect::<Result<Vec<_>, _>>()?,
            order_by: order_by
                .into_iter()
                .map(lower_order_by_expr)
                .collect::<Result<Vec<_>, _>>()?,
            frame: frame.map(lower_window_frame),
        }),
        ast::Expr::NextValueFor { sequence_name } => {
            Ok(executor_ast::expressions::Expr::NextValueFor {
                sequence_name: lower_object_name(sequence_name),
            })
        }
    }
}

pub fn lower_binary_op(op: ast::BinaryOp) -> Result<executor_ast::expressions::BinaryOp, DbError> {
    match op {
        ast::BinaryOp::Eq => Ok(executor_ast::expressions::BinaryOp::Eq),
        ast::BinaryOp::NotEq => Ok(executor_ast::expressions::BinaryOp::NotEq),
        ast::BinaryOp::Gt => Ok(executor_ast::expressions::BinaryOp::Gt),
        ast::BinaryOp::Lt => Ok(executor_ast::expressions::BinaryOp::Lt),
        ast::BinaryOp::Gte => Ok(executor_ast::expressions::BinaryOp::Gte),
        ast::BinaryOp::Lte => Ok(executor_ast::expressions::BinaryOp::Lte),
        ast::BinaryOp::And => Ok(executor_ast::expressions::BinaryOp::And),
        ast::BinaryOp::Or => Ok(executor_ast::expressions::BinaryOp::Or),
        ast::BinaryOp::Add => Ok(executor_ast::expressions::BinaryOp::Add),
        ast::BinaryOp::Subtract => Ok(executor_ast::expressions::BinaryOp::Subtract),
        ast::BinaryOp::Multiply => Ok(executor_ast::expressions::BinaryOp::Multiply),
        ast::BinaryOp::Divide => Ok(executor_ast::expressions::BinaryOp::Divide),
        ast::BinaryOp::Modulo => Ok(executor_ast::expressions::BinaryOp::Modulo),
        ast::BinaryOp::BitwiseAnd => Ok(executor_ast::expressions::BinaryOp::BitwiseAnd),
        ast::BinaryOp::BitwiseOr => Ok(executor_ast::expressions::BinaryOp::BitwiseOr),
        ast::BinaryOp::BitwiseXor => Ok(executor_ast::expressions::BinaryOp::BitwiseXor),
        ast::BinaryOp::Like => Err(DbError::Parse(
            "LIKE should be lowered as a dedicated expression".into(),
        )),
    }
}

pub fn lower_unary_op(op: ast::UnaryOp) -> executor_ast::expressions::UnaryOp {
    match op {
        ast::UnaryOp::Negate => executor_ast::expressions::UnaryOp::Negate,
        ast::UnaryOp::Not => executor_ast::expressions::UnaryOp::Not,
        ast::UnaryOp::BitwiseNot => executor_ast::expressions::UnaryOp::BitwiseNot,
    }
}

pub fn lower_window_func(name: &str) -> executor_ast::expressions::WindowFunc {
    match name.to_uppercase().as_str() {
        "ROW_NUMBER" => executor_ast::expressions::WindowFunc::RowNumber,
        "RANK" => executor_ast::expressions::WindowFunc::Rank,
        "DENSE_RANK" => executor_ast::expressions::WindowFunc::DenseRank,
        "NTILE" => executor_ast::expressions::WindowFunc::NTile,
        "LAG" => executor_ast::expressions::WindowFunc::Lag,
        "LEAD" => executor_ast::expressions::WindowFunc::Lead,
        "FIRST_VALUE" => executor_ast::expressions::WindowFunc::FirstValue,
        "LAST_VALUE" => executor_ast::expressions::WindowFunc::LastValue,
        "PERCENTILE_CONT" => executor_ast::expressions::WindowFunc::PercentileCont,
        "PERCENTILE_DISC" => executor_ast::expressions::WindowFunc::PercentileDisc,
        "PERCENT_RANK" => executor_ast::expressions::WindowFunc::PercentileRank,
        _ => executor_ast::expressions::WindowFunc::Aggregate(name.to_string()),
    }
}

pub fn lower_window_frame(frame: ast::WindowFrame) -> executor_ast::expressions::WindowFrame {
    executor_ast::expressions::WindowFrame {
        units: match frame.units {
            ast::WindowFrameUnits::Rows => executor_ast::expressions::WindowFrameUnits::Rows,
            ast::WindowFrameUnits::Range => executor_ast::expressions::WindowFrameUnits::Range,
            ast::WindowFrameUnits::Groups => executor_ast::expressions::WindowFrameUnits::Groups,
        },
        extent: match frame.extent {
            ast::WindowFrameExtent::Bound(b) => {
                executor_ast::expressions::WindowFrameExtent::Bound(lower_window_bound(b))
            }
            ast::WindowFrameExtent::Between(b1, b2) => {
                executor_ast::expressions::WindowFrameExtent::Between(
                    lower_window_bound(b1),
                    lower_window_bound(b2),
                )
            }
        },
    }
}

pub fn lower_window_bound(
    bound: ast::WindowFrameBound,
) -> executor_ast::expressions::WindowFrameBound {
    match bound {
        ast::WindowFrameBound::UnboundedPreceding => {
            executor_ast::expressions::WindowFrameBound::UnboundedPreceding
        }
        ast::WindowFrameBound::Preceding(n) => {
            executor_ast::expressions::WindowFrameBound::Preceding(n)
        }
        ast::WindowFrameBound::CurrentRow => {
            executor_ast::expressions::WindowFrameBound::CurrentRow
        }
        ast::WindowFrameBound::Following(n) => {
            executor_ast::expressions::WindowFrameBound::Following(n)
        }
        ast::WindowFrameBound::UnboundedFollowing => {
            executor_ast::expressions::WindowFrameBound::UnboundedFollowing
        }
    }
}

pub fn lower_object_name(mut parts: Vec<String>) -> executor_ast::ObjectName {
    match parts.len() {
        0 => executor_ast::ObjectName {
            database: None,
            schema: None,
            name: "".to_string(),
        },
        1 => executor_ast::ObjectName {
            database: None,
            schema: None,
            name: parts.remove(0),
        },
        2 => {
            let name = parts.pop().unwrap_or_default();
            let schema = Some(parts.pop().unwrap_or_default());
            executor_ast::ObjectName {
                database: None,
                schema,
                name,
            }
        }
        _ => {
            let name = parts.pop().unwrap_or_default();
            let schema = Some(parts.pop().unwrap_or_default());
            let database = Some(parts.pop().unwrap_or_default());
            executor_ast::ObjectName {
                database,
                schema,
                name,
            }
        }
    }
}

pub fn lower_object_name_owned(name: ast::ObjectName) -> executor_ast::ObjectName {
    executor_ast::ObjectName {
        database: name.database,
        schema: name.schema,
        name: name.name,
    }
}

pub fn lower_data_type(
    dt: ast::DataType,
) -> Result<executor_ast::data_types::DataTypeSpec, DbError> {
    match dt {
        ast::DataType::Int => Ok(executor_ast::data_types::DataTypeSpec::Int),
        ast::DataType::BigInt => Ok(executor_ast::data_types::DataTypeSpec::BigInt),
        ast::DataType::SmallInt => Ok(executor_ast::data_types::DataTypeSpec::SmallInt),
        ast::DataType::TinyInt => Ok(executor_ast::data_types::DataTypeSpec::TinyInt),
        ast::DataType::Bit => Ok(executor_ast::data_types::DataTypeSpec::Bit),
        ast::DataType::Float => Ok(executor_ast::data_types::DataTypeSpec::Float),
        ast::DataType::Real => Ok(executor_ast::data_types::DataTypeSpec::Float),
        ast::DataType::Decimal(p, s) => Ok(executor_ast::data_types::DataTypeSpec::Decimal(p, s)),
        ast::DataType::Numeric(p, s) => Ok(executor_ast::data_types::DataTypeSpec::Numeric(p, s)),
        ast::DataType::VarChar(n) => Ok(executor_ast::data_types::DataTypeSpec::VarChar(
            n.unwrap_or(u16::MAX as u32) as u16,
        )),
        ast::DataType::NVarChar(n) => Ok(executor_ast::data_types::DataTypeSpec::NVarChar(
            n.unwrap_or(u16::MAX as u32) as u16,
        )),
        ast::DataType::Char(n) => Ok(executor_ast::data_types::DataTypeSpec::Char(
            n.unwrap_or(1) as u16
        )),
        ast::DataType::NChar(n) => Ok(executor_ast::data_types::DataTypeSpec::NChar(
            n.unwrap_or(1) as u16,
        )),
        ast::DataType::Binary(n) => Ok(executor_ast::data_types::DataTypeSpec::Binary(
            n.unwrap_or(1) as u16,
        )),
        ast::DataType::VarBinary(n) => Ok(executor_ast::data_types::DataTypeSpec::VarBinary(
            n.unwrap_or(u16::MAX as u32) as u16,
        )),
        ast::DataType::Vector(dimensions) => {
            Ok(executor_ast::data_types::DataTypeSpec::Vector(dimensions))
        }
        ast::DataType::Date => Ok(executor_ast::data_types::DataTypeSpec::Date),
        ast::DataType::Time => Ok(executor_ast::data_types::DataTypeSpec::Time),
        ast::DataType::DateTime => Ok(executor_ast::data_types::DataTypeSpec::DateTime),
        ast::DataType::DateTime2 => Ok(executor_ast::data_types::DataTypeSpec::DateTime2),
        ast::DataType::Money => Ok(executor_ast::data_types::DataTypeSpec::Money),
        ast::DataType::SmallMoney => Ok(executor_ast::data_types::DataTypeSpec::SmallMoney),
        ast::DataType::UniqueIdentifier => {
            Ok(executor_ast::data_types::DataTypeSpec::UniqueIdentifier)
        }
        ast::DataType::SqlVariant => Ok(executor_ast::data_types::DataTypeSpec::SqlVariant),
        ast::DataType::Xml => Ok(executor_ast::data_types::DataTypeSpec::Xml),
        ast::DataType::DateTimeOffset => Ok(executor_ast::data_types::DataTypeSpec::DateTimeOffset),
        ast::DataType::SmallDateTime => Ok(executor_ast::data_types::DataTypeSpec::SmallDateTime),
        ast::DataType::Image => Ok(executor_ast::data_types::DataTypeSpec::VarBinary(8000)),
        ast::DataType::Text => Ok(executor_ast::data_types::DataTypeSpec::VarChar(8000)),
        ast::DataType::NText => Ok(executor_ast::data_types::DataTypeSpec::NVarChar(4000)),
        ast::DataType::Table => Ok(executor_ast::data_types::DataTypeSpec::VarChar(255)),
        ast::DataType::Custom(_) => Ok(executor_ast::data_types::DataTypeSpec::VarChar(255)),
    }
}

pub fn lower_order_by_expr(
    o: ast::OrderByExpr,
) -> Result<executor_ast::statements::query::OrderByExpr, DbError> {
    Ok(executor_ast::statements::query::OrderByExpr {
        expr: lower_expr(o.expr)?,
        asc: o.asc,
    })
}
