use std::fmt::Write as _;
use std::marker::PhantomData;
use tokio_postgres::types::ToSql;

use super::QueryBindings;
use super::TableAlias;
use crate::entity::EntityColumn;

pub type BoxedQueryExpression<'a> = Box<dyn 'a + QueryExpression<'a>>;

impl Clone for BoxedQueryExpression<'_> {
    fn clone(&self) -> Self {
        return self.clone_boxed();
    }
}

pub trait QueryExpression<'a>: Send + Sync {
    fn add_to_query(&self, query: &mut String, bindings: &mut QueryBindings<'a>);

    fn clone_boxed(&self) -> BoxedQueryExpression<'a>;
}

#[derive(Clone)]
pub struct ColumnExpression {
    table: &'static str,
    column: &'static str,
}

impl<'a> QueryExpression<'a> for ColumnExpression {
    fn add_to_query(&self, query: &mut String, _bindings: &mut QueryBindings<'a>) {
        write!(query, "{}.{}", self.table, self.column).unwrap();
    }

    fn clone_boxed(&self) -> BoxedQueryExpression<'a> {
        return Box::new(self.clone());
    }
}

#[derive(Clone)]
pub struct ReferenceExpression<'a, T> {
    value: &'a T,
}

impl<'a, T: Send + Sync + ToSql> QueryExpression<'a> for ReferenceExpression<'a, T> {
    fn add_to_query(&self, query: &mut String, bindings: &mut QueryBindings<'a>) {
        write!(query, "${}", bindings.add(self.value)).unwrap();
    }

    fn clone_boxed(&self) -> BoxedQueryExpression<'a> {
        return Box::new(ReferenceExpression { value: self.value });
    }
}

#[derive(Clone)]
pub struct ValueExpression<'a, T: 'a + Clone + Send + Sync + ToSql> {
    value: T,
    _lives_for_a: PhantomData<&'a T>,
}

impl<'a, T: 'a + Clone + Send + Sync + ToSql> QueryExpression<'a> for ValueExpression<'a, T> {
    fn add_to_query(&self, query: &mut String, bindings: &mut QueryBindings<'a>) {
        let boxed = Box::new(self.value.clone());
        write!(query, "${}", bindings.add_boxed(boxed)).unwrap();
    }

    fn clone_boxed(&self) -> BoxedQueryExpression<'a> {
        return Box::new(self.clone());
    }
}

#[derive(Clone)]
pub struct TupleExpression<'a> {
    values: Vec<BoxedQueryExpression<'a>>,
}

impl<'a> QueryExpression<'a> for TupleExpression<'a> {
    fn add_to_query(&self, query: &mut String, bindings: &mut QueryBindings<'a>) {
        query.write_char('(').unwrap();

        if !self.values.is_empty() {
            self.values[0].add_to_query(query, bindings);

            for val in &self.values[1..] {
                query.write_str(", ").unwrap();
                val.add_to_query(query, bindings);
            }
        }

        query.write_char(')').unwrap();
    }

    fn clone_boxed(&self) -> BoxedQueryExpression<'a> {
        return Box::new(self.clone());
    }
}

#[derive(Clone)]
pub struct PrefixOpExpression<'a> {
    op: &'static str,
    expr: BoxedQueryExpression<'a>,
}

impl<'a> QueryExpression<'a> for PrefixOpExpression<'a> {
    fn add_to_query(&self, query: &mut String, bindings: &mut QueryBindings<'a>) {
        query.write_char('(').unwrap();
        write!(query, " {} ", self.op).unwrap();
        self.expr.add_to_query(query, bindings);
        query.write_char(')').unwrap();
    }

    fn clone_boxed(&self) -> BoxedQueryExpression<'a> {
        return Box::new(self.clone());
    }
}

#[derive(Clone)]
pub struct PostfixOpExpression<'a> {
    expr: BoxedQueryExpression<'a>,
    op: &'static str,
}

impl<'a> QueryExpression<'a> for PostfixOpExpression<'a> {
    fn add_to_query(&self, query: &mut String, bindings: &mut QueryBindings<'a>) {
        query.write_char('(').unwrap();
        self.expr.add_to_query(query, bindings);
        write!(query, " {} ", self.op).unwrap();
        query.write_char(')').unwrap();
    }

    fn clone_boxed(&self) -> BoxedQueryExpression<'a> {
        return Box::new(self.clone());
    }
}

#[derive(Clone)]
pub struct BinaryOpExpression<'a> {
    left: BoxedQueryExpression<'a>,
    op: &'static str,
    right: BoxedQueryExpression<'a>,
}

impl<'a> QueryExpression<'a> for BinaryOpExpression<'a> {
    fn add_to_query(&self, query: &mut String, bindings: &mut QueryBindings<'a>) {
        query.write_char('(').unwrap();
        self.left.add_to_query(query, bindings);
        write!(query, " {} ", self.op).unwrap();
        self.right.add_to_query(query, bindings);
        query.write_char(')').unwrap();
    }

    fn clone_boxed(&self) -> BoxedQueryExpression<'a> {
        return Box::new(self.clone());
    }
}

#[derive(Clone)]
pub struct TemplateExpression<'a> {
    template: &'static str,
    args: Vec<BoxedQueryExpression<'a>>,
}

impl<'a> QueryExpression<'a> for TemplateExpression<'a> {
    fn add_to_query(&self, query: &mut String, bindings: &mut QueryBindings<'a>) {
        for (n, segment) in self.template.split('?').enumerate() {
            if n > 0 {
                self.args[n - 1].add_to_query(query, bindings);
            }

            query.write_str(segment).unwrap();
        }
    }

    fn clone_boxed(&self) -> BoxedQueryExpression<'a> {
        return Box::new(self.clone());
    }
}

#[allow(non_snake_case)]
pub mod Q {
    use super::*;

    pub fn col<'a>(table: &impl TableAlias, column: &EntityColumn) -> BoxedQueryExpression<'a> {
        return Box::new(ColumnExpression {
            table: table.alias(),
            column: column.name,
        });
    }

    // Unfortunately `ref` is a keyword :<
    pub fn refer<T: Send + Sync + ToSql>(value: &T) -> BoxedQueryExpression {
        return Box::new(ReferenceExpression { value });
    }

    pub fn val<'a, T: 'a + Clone + Send + Sync + ToSql>(value: T) -> BoxedQueryExpression<'a> {
        return Box::new(ValueExpression {
            value,
            _lives_for_a: PhantomData,
        });
    }

    pub fn tup(array: Vec<BoxedQueryExpression>) -> BoxedQueryExpression {
        return Box::new(TupleExpression { values: array });
    }

    pub fn fill<'a>(
        template: &'static str,
        args: Vec<BoxedQueryExpression<'a>>,
    ) -> BoxedQueryExpression<'a> {
        let num_params = template.chars().filter(|c| c == &'?').count();
        let num_args = args.len();
        if num_args != num_params {
            panic!("Expected {num_params} template arguments, got {num_args}");
        }

        return Box::new(TemplateExpression { template, args });
    }

    pub fn pre_op<'a>(
        op: &'static str,
        expr: BoxedQueryExpression<'a>,
    ) -> BoxedQueryExpression<'a> {
        return Box::new(PrefixOpExpression { op, expr });
    }

    pub fn post_op<'a>(
        expr: BoxedQueryExpression<'a>,
        op: &'static str,
    ) -> BoxedQueryExpression<'a> {
        return Box::new(PostfixOpExpression { expr, op });
    }

    pub fn op<'a>(
        left: BoxedQueryExpression<'a>,
        op: &'static str,
        right: BoxedQueryExpression<'a>,
    ) -> BoxedQueryExpression<'a> {
        return Box::new(BinaryOpExpression { left, op, right });
    }
}
