use crate::sql::expression::identifier::SimpleIdentifier;
use crate::sql::expression::operator::Operator;
use crate::sql::expression::precedence::precedence;
use crate::sql::expression::SQLExpression;
use crate::write_utils::{maybe_newline, maybe_pad, newline_or_space, Indent};
use ordermap::OrderMap;
use std::fmt;
use std::fmt::{Display, Formatter};

use super::OrderByExpression;

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct FunctionCallApply {
    pub function_name: String,
    pub arguments: Vec<SQLExpression>,
    pub named_arguments: OrderMap<String, SQLExpression>,
    pub order_by: Vec<OrderByExpression>,
    pub ignore_nulls: bool,
    pub distinct: bool,
}

impl FunctionCallApply {
    pub fn with_positional(function_name: &str, arguments: Vec<SQLExpression>) -> Self {
        Self {
            function_name: function_name.to_string(),
            arguments: arguments.to_vec(),
            ..FunctionCallApply::default()
        }
    }

    pub fn with_no_arguments(function_name: &str) -> Self {
        Self {
            function_name: function_name.to_string(),
            ..FunctionCallApply::default()
        }
    }

    pub fn with_one(function_name: &str, argument: SQLExpression) -> Self {
        Self {
            function_name: function_name.to_string(),
            arguments: vec![argument],
            ..FunctionCallApply::default()
        }
    }

    pub fn with_two(
        function_name: &str,
        argument1: SQLExpression,
        argument2: SQLExpression,
    ) -> Self {
        Self {
            function_name: function_name.to_string(),
            arguments: vec![argument1, argument2],
            ..FunctionCallApply::default()
        }
    }

    pub fn with_three(
        function_name: &str,
        argument1: SQLExpression,
        argument2: SQLExpression,
        argument3: SQLExpression,
    ) -> Self {
        Self {
            function_name: function_name.to_string(),
            arguments: vec![argument1, argument2, argument3],
            ..FunctionCallApply::default()
        }
    }

    pub fn with_four(
        function_name: &str,
        argument1: SQLExpression,
        argument2: SQLExpression,
        argument3: SQLExpression,
        argument4: SQLExpression,
    ) -> Self {
        Self {
            function_name: function_name.to_string(),
            arguments: vec![argument1, argument2, argument3, argument4],
            ..FunctionCallApply::default()
        }
    }

    pub fn with_order_by(mut self, mut order_by: Vec<OrderByExpression>) -> Self {
        self.order_by.append(&mut order_by);
        self
    }

    pub fn with_ignore_nulls(mut self) -> Self {
        self.ignore_nulls = true;
        self
    }

    pub fn with_distinct(mut self) -> Self {
        self.distinct = true;
        self
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        let indentation = match indentation {
            Indent::Pretty { .. } if self.subelements() > 3 => indentation,
            _ => Indent::Compact,
        };

        let has_args = !self.arguments.is_empty();
        let has_named = !self.named_arguments.is_empty();
        let has_order_by = !self.order_by.is_empty();
        let has_content = has_args || has_named || has_order_by || self.distinct;

        write!(f, "{}(", self.function_name)?;
        if has_content {
            maybe_newline(f, indentation)?;
        }
        if self.distinct {
            maybe_pad(f, indentation.nested())?;
            write!(f, "DISTINCT")?;
            newline_or_space(f, indentation)?;
        }
        for (i, arg) in self.arguments.iter().enumerate() {
            maybe_pad(f, indentation.nested())?;
            arg.fmt_indented(f, indentation.nested())?;
            if i < self.arguments.len() - 1 {
                write!(f, ",")?;
                newline_or_space(f, indentation)?;
            }
        }
        if has_args && has_named {
            newline_or_space(f, indentation)?;
        }
        for (i, (key, value)) in self.named_arguments.iter().enumerate() {
            maybe_pad(f, indentation.nested())?;
            write!(f, "{} ", key)?;
            value.fmt_indented(f, indentation.nested())?;
            if i < self.named_arguments.len() - 1 {
                newline_or_space(f, indentation)?;
            }
        }

        if has_order_by {
            newline_or_space(f, indentation)?;
            maybe_pad(f, indentation.nested())?;
            write!(f, "ORDER BY")?;
            newline_or_space(f, indentation)?;
            for (i, order) in self.order_by.iter().enumerate() {
                maybe_pad(f, indentation.nested().nested())?;
                order.fmt_indented(f, indentation.nested().nested())?;
                if i < self.order_by.len() - 1 {
                    write!(f, ",")?;
                    newline_or_space(f, indentation)?;
                }
            }
        }

        if has_content {
            maybe_newline(f, indentation)?;
            maybe_pad(f, indentation)?;
        }
        write!(f, ")")?;

        if self.ignore_nulls {
            write!(f, " IGNORE NULLS")?;
        }

        Ok(())
    }

    pub(crate) fn subelements(&self) -> usize {
        self.arguments
            .iter()
            .map(|a| a.subelements() + 1)
            .sum::<usize>()
            + self
                .named_arguments
                .iter()
                .map(|(_, v)| v.subelements() + 1)
                .sum::<usize>()
            + self
                .order_by
                .iter()
                .map(|obe| obe.expression.subelements() + 1)
                .sum::<usize>()
    }
}

impl Display for FunctionCallApply {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lambda {
    pub arguments: Vec<SimpleIdentifier>,
    pub body: Box<SQLExpression>,
}

impl Lambda {
    pub fn new(arguments: Vec<SimpleIdentifier>, body: SQLExpression) -> Self {
        Self {
            arguments,
            body: Box::new(body),
        }
    }

    pub fn from_single_argument(argument: SimpleIdentifier, body: SQLExpression) -> Self {
        Self {
            arguments: vec![argument],
            body: Box::new(body),
        }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        write!(
            f,
            "({}) -> ",
            self.arguments
                .iter()
                .map(|a| a.to_string())
                .collect::<Vec<String>>()
                .join(", "),
        )?;
        self.body.fmt_indented(f, indentation)
    }
}

impl Display for Lambda {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BinaryOperatorApply {
    pub operator: Operator,
    pub left: Box<SQLExpression>,
    pub right: Box<SQLExpression>,
}

impl BinaryOperatorApply {
    pub fn new(operator: Operator, left: SQLExpression, right: SQLExpression) -> Self {
        Self {
            operator,
            left: Box::new(left),
            right: Box::new(right),
        }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        let self_expression: SQLExpression = self.clone().into();
        let parens_left = precedence(self.left.as_ref())
            .and_then(|l| precedence(&self_expression).map(|t| t < l))
            .unwrap_or(false);

        let parens_right = precedence(self.right.as_ref())
            .and_then(|r| precedence(&self_expression).map(|t| t < r))
            .unwrap_or(false);

        if parens_left {
            write!(f, "(")?;
        }
        self.left.fmt_indented(f, indentation)?;
        if parens_left {
            write!(f, ")")?;
        }

        write!(f, " {} ", self.operator)?;

        if parens_right {
            write!(f, "(")?;
        }
        self.right.fmt_indented(f, indentation)?;
        if parens_right {
            write!(f, ")")?;
        }

        Ok(())
    }
}

impl Display for BinaryOperatorApply {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnaryOperatorApply {
    pub operator: Operator,
    pub operand: Box<SQLExpression>,
}

impl UnaryOperatorApply {
    pub fn new(operator: Operator, operand: SQLExpression) -> Self {
        Self {
            operator,
            operand: Box::new(operand),
        }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, _indentation: Indent) -> fmt::Result {
        let this: SQLExpression = self.clone().into();
        let expression_render = self.operand.to_string();

        let parens_expr = precedence(self.operand.as_ref())
            .and_then(|e| precedence(&this).map(|t| t <= e))
            .map(|b| {
                if b {
                    format!("({})", expression_render)
                } else {
                    expression_render.clone()
                }
            })
            .unwrap_or(expression_render);

        write!(f, "{} {}", self.operator.to_string(), parens_expr)
    }
}

impl Display for UnaryOperatorApply {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}
