use std::borrow::Cow;

use crate::{
    ConditionHolder, ConditionalStatement, DynIden, Expr, IntoColumnRef, IntoCondition, IntoIden,
    IntoTableRef, LogicalChainOper, NullOrdering, Order, OrderExpr, OrderedStatement, QueryBuilder,
    QueryStatement, QueryStatementBuilder, QueryStatementWriter, ReturningClause, SqlWriter,
    SubQueryStatement, TableRef, Value, Values, WithClause, WithQuery,
};

/// Update existing rows in the table
///
/// # Examples
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::update()
///     .table(Glyph::Table)
///     .values([(Glyph::Aspect, 1.23.into()), (Glyph::Image, "123".into())])
///     .and_where(Expr::col(Glyph::Id).eq(1))
///     .to_owned();
///
/// assert_eq!(
///     query.to_string(MysqlQueryBuilder),
///     r#"UPDATE `glyph` SET `aspect` = 1.23, `image` = '123' WHERE `id` = 1"#
/// );
/// assert_eq!(
///     query.to_string(PostgresQueryBuilder),
///     r#"UPDATE "glyph" SET "aspect" = 1.23, "image" = '123' WHERE "id" = 1"#
/// );
/// assert_eq!(
///     query.to_string(SqliteQueryBuilder),
///     r#"UPDATE "glyph" SET "aspect" = 1.23, "image" = '123' WHERE "id" = 1"#
/// );
/// ```
#[derive(Default, Debug, Clone, PartialEq)]
pub struct UpdateStatement {
    pub(crate) table: Option<Box<TableRef>>,
    pub(crate) from: Vec<TableRef>,
    pub(crate) values: Vec<(DynIden, Box<Expr>)>,
    pub(crate) r#where: ConditionHolder,
    pub(crate) orders: Vec<OrderExpr>,
    pub(crate) limit: Option<Value>,
    pub(crate) returning: Option<ReturningClause>,
    pub(crate) with: Option<WithClause>,
}

impl UpdateStatement {
    /// Construct a new [`UpdateStatement`]
    pub fn new() -> Self {
        Self::default()
    }

    pub fn take(&mut self) -> Self {
        Self {
            table: self.table.take(),
            from: std::mem::take(&mut self.from),
            values: std::mem::take(&mut self.values),
            r#where: std::mem::take(&mut self.r#where),
            orders: std::mem::take(&mut self.orders),
            limit: self.limit.take(),
            returning: self.returning.take(),
            with: self.with.take(),
        }
    }

    /// Specify which table to update.
    ///
    /// # Examples
    ///
    /// See [`UpdateStatement::values`]
    #[allow(clippy::wrong_self_convention)]
    pub fn table<T>(&mut self, tbl_ref: T) -> &mut Self
    where
        T: IntoTableRef,
    {
        self.table = Some(Box::new(tbl_ref.into_table_ref()));
        self
    }

    /// Update using data from another table (`UPDATE .. FROM ..`).
    ///
    /// # MySQL Notes
    ///
    /// MySQL doesn't support the UPDATE FROM syntax. And the current implementation attempt to tranform it to the UPDATE JOIN syntax,
    /// which only works for one join target.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{audit::*, tests_cfg::*, *};
    ///
    /// let query = Query::update()
    ///     .table(Glyph::Table)
    ///     .value(Glyph::Tokens, Expr::column((Char::Table, Char::Character)))
    ///     .from(Char::Table)
    ///     .cond_where(
    ///         Expr::col((Glyph::Table, Glyph::Image))
    ///             .eq(Expr::col((Char::Table, Char::UserData))),
    ///     )
    ///     .to_owned();
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     "UPDATE `glyph` JOIN `character` ON `glyph`.`image` = `character`.`user_data` SET `glyph`.`tokens` = `character`.`character`"
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"UPDATE "glyph" SET "tokens" = "character"."character" FROM "character" WHERE "glyph"."image" = "character"."user_data""#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"UPDATE "glyph" SET "tokens" = "character"."character" FROM "character" WHERE "glyph"."image" = "character"."user_data""#
    /// );
    /// assert_eq!(
    ///     query.audit().unwrap().updated_tables(),
    ///     [Glyph::Table.into_iden()]
    /// );
    /// assert_eq!(
    ///     query.audit().unwrap().selected_tables(),
    ///     [Char::Table.into_iden()]
    /// );
    /// ```
    pub fn from<R>(&mut self, tbl_ref: R) -> &mut Self
    where
        R: IntoTableRef,
    {
        self.from_from(tbl_ref.into_table_ref())
    }

    #[allow(clippy::wrong_self_convention)]
    fn from_from(&mut self, select: TableRef) -> &mut Self {
        self.from.push(select);
        self
    }

    /// Update column values. To set multiple column-value pairs at once.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{audit::*, tests_cfg::*, *};
    ///
    /// let query = Query::update()
    ///     .table(Glyph::Table)
    ///     .values([
    ///         (Glyph::Aspect, 2.1345.into()),
    ///         (Glyph::Image, "235m".into()),
    ///     ])
    ///     .to_owned();
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     r#"UPDATE `glyph` SET `aspect` = 2.1345, `image` = '235m'"#
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 2.1345, "image" = '235m'"#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 2.1345, "image" = '235m'"#
    /// );
    /// assert_eq!(
    ///     query.audit().unwrap().updated_tables(),
    ///     [Glyph::Table.into_iden()]
    /// );
    /// assert_eq!(query.audit().unwrap().selected_tables(), []);
    /// ```
    pub fn values<T, I>(&mut self, values: I) -> &mut Self
    where
        T: IntoIden,
        I: IntoIterator<Item = (T, Expr)>,
    {
        for (k, v) in values.into_iter() {
            self.values.push((k.into_iden(), Box::new(v)));
        }
        self
    }

    /// Update column value by [`Expr`].
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{*, tests_cfg::*};
    ///
    /// let query = Query::update()
    ///     .table(Glyph::Table)
    ///     .value(Glyph::Aspect, Expr::cust("60 * 24 * 24"))
    ///     .values([
    ///         (Glyph::Image, "24B0E11951B03B07F8300FD003983F03F0780060".into()),
    ///     ])
    ///     .to_owned();
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     r#"UPDATE `glyph` SET `aspect` = 60 * 24 * 24, `image` = '24B0E11951B03B07F8300FD003983F03F0780060'"#
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 60 * 24 * 24, "image" = '24B0E11951B03B07F8300FD003983F03F0780060'"#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 60 * 24 * 24, "image" = '24B0E11951B03B07F8300FD003983F03F0780060'"#
    /// );
    ///
    /// let query = Query::update()
    ///     .table(Glyph::Table)
    ///     .value(Glyph::Aspect, Expr::value(Value::Int(None)))
    ///     .to_owned();
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     r#"UPDATE `glyph` SET `aspect` = NULL"#
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = NULL"#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = NULL"#
    /// );
    /// ```
    pub fn value<C, T>(&mut self, col: C, value: T) -> &mut Self
    where
        C: IntoIden,
        T: Into<Expr>,
    {
        self.values.push((col.into_iden(), Box::new(value.into())));
        self
    }

    /// Limit number of updated rows.
    pub fn limit(&mut self, limit: u64) -> &mut Self {
        self.limit = Some(limit.into());
        self
    }

    /// RETURNING expressions.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{audit::*, tests_cfg::*, *};
    ///
    /// let query = Query::update()
    ///     .table(Glyph::Table)
    ///     .value(Glyph::Aspect, 2.1345)
    ///     .value(Glyph::Image, "235m")
    ///     .returning(Query::returning().columns([Glyph::Id]))
    ///     .to_owned();
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     r#"UPDATE `glyph` SET `aspect` = 2.1345, `image` = '235m'"#
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 2.1345, "image" = '235m' RETURNING "id""#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 2.1345, "image" = '235m' RETURNING "id""#
    /// );
    /// assert_eq!(
    ///     query.audit().unwrap().updated_tables(),
    ///     [Glyph::Table.into_iden()]
    /// );
    /// assert_eq!(
    ///     query.audit().unwrap().selected_tables(),
    ///     [Glyph::Table.into_iden()]
    /// );
    /// ```
    pub fn returning(&mut self, returning: ReturningClause) -> &mut Self {
        self.returning = Some(returning);
        self
    }

    /// RETURNING expressions for a column.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// let query = Query::update()
    ///     .table(Glyph::Table)
    ///     .table(Glyph::Table)
    ///     .value(Glyph::Aspect, 2.1345)
    ///     .value(Glyph::Image, "235m")
    ///     .returning_col(Glyph::Id)
    ///     .to_owned();
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     r#"UPDATE `glyph` SET `aspect` = 2.1345, `image` = '235m'"#
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 2.1345, "image" = '235m' RETURNING "id""#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 2.1345, "image" = '235m' RETURNING "id""#
    /// );
    /// ```
    pub fn returning_col<C>(&mut self, col: C) -> &mut Self
    where
        C: IntoColumnRef,
    {
        self.returning(ReturningClause::Columns(vec![col.into_column_ref()]))
    }

    /// RETURNING expressions all columns.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{tests_cfg::*, *};
    ///
    /// let query = Query::update()
    ///     .table(Glyph::Table)
    ///     .table(Glyph::Table)
    ///     .value(Glyph::Aspect, 2.1345)
    ///     .value(Glyph::Image, "235m")
    ///     .returning_all()
    ///     .to_owned();
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     r#"UPDATE `glyph` SET `aspect` = 2.1345, `image` = '235m'"#
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 2.1345, "image" = '235m' RETURNING *"#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"UPDATE "glyph" SET "aspect" = 2.1345, "image" = '235m' RETURNING *"#
    /// );
    /// ```
    pub fn returning_all(&mut self) -> &mut Self {
        self.returning(ReturningClause::All)
    }

    /// Create a [WithQuery] by specifying a [WithClause] to execute this query with.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{IntoCondition, IntoIden, audit::*, tests_cfg::*, *};
    ///
    /// let select = SelectStatement::new()
    ///         .columns([Glyph::Id])
    ///         .from(Glyph::Table)
    ///         .and_where(Expr::col(Glyph::Image).like("0%"))
    ///         .to_owned();
    ///     let cte = CommonTableExpression::new()
    ///         .query(select)
    ///         .column(Glyph::Id)
    ///         .table_name("cte")
    ///         .to_owned();
    ///     let with_clause = WithClause::new().cte(cte).to_owned();
    ///     let update = UpdateStatement::new()
    ///         .table(Glyph::Table)
    ///         .and_where(Expr::col(Glyph::Id).in_subquery(SelectStatement::new().column(Glyph::Id).from("cte").to_owned()))
    ///         .value(Glyph::Aspect, Expr::cust("60 * 24 * 24"))
    ///         .to_owned();
    ///     let query = update.with(with_clause);
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     r#"WITH `cte` (`id`) AS (SELECT `id` FROM `glyph` WHERE `image` LIKE '0%') UPDATE `glyph` SET `aspect` = 60 * 24 * 24 WHERE `id` IN (SELECT `id` FROM `cte`)"#
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"WITH "cte" ("id") AS (SELECT "id" FROM "glyph" WHERE "image" LIKE '0%') UPDATE "glyph" SET "aspect" = 60 * 24 * 24 WHERE "id" IN (SELECT "id" FROM "cte")"#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"WITH "cte" ("id") AS (SELECT "id" FROM "glyph" WHERE "image" LIKE '0%') UPDATE "glyph" SET "aspect" = 60 * 24 * 24 WHERE "id" IN (SELECT "id" FROM "cte")"#
    /// );
    /// assert_eq!(
    ///     query.audit_unwrap().updated_tables(),
    ///     [Glyph::Table.into_iden()]
    /// );
    /// assert_eq!(
    ///     query.audit_unwrap().selected_tables(),
    ///     [Glyph::Table.into_iden()]
    /// );
    /// ```
    pub fn with(self, clause: WithClause) -> WithQuery {
        clause.query(self)
    }

    /// Create a Common Table Expression by specifying a [CommonTableExpression][crate::CommonTableExpression]
    /// or [WithClause] to execute this query with.
    ///
    /// # Examples
    ///
    /// ```
    /// use sea_query::{IntoCondition, IntoIden, audit::*, tests_cfg::*, *};
    ///
    /// let select = SelectStatement::new()
    ///         .columns([Glyph::Id])
    ///         .from(Glyph::Table)
    ///         .and_where(Expr::col(Glyph::Image).like("0%"))
    ///         .to_owned();
    ///     let cte = CommonTableExpression::new()
    ///         .query(select)
    ///         .column(Glyph::Id)
    ///         .table_name("cte")
    ///         .to_owned();
    ///     let with_clause = WithClause::new().cte(cte).to_owned();
    ///     let query = UpdateStatement::new()
    ///         .table(Glyph::Table)
    ///         .and_where(Expr::col(Glyph::Id).in_subquery(SelectStatement::new().column(Glyph::Id).from("cte").to_owned()))
    ///         .value(Glyph::Aspect, Expr::cust("60 * 24 * 24"))
    ///         .with_cte(with_clause)
    ///         .to_owned();
    ///
    /// assert_eq!(
    ///     query.to_string(MysqlQueryBuilder),
    ///     r#"WITH `cte` (`id`) AS (SELECT `id` FROM `glyph` WHERE `image` LIKE '0%') UPDATE `glyph` SET `aspect` = 60 * 24 * 24 WHERE `id` IN (SELECT `id` FROM `cte`)"#
    /// );
    /// assert_eq!(
    ///     query.to_string(PostgresQueryBuilder),
    ///     r#"WITH "cte" ("id") AS (SELECT "id" FROM "glyph" WHERE "image" LIKE '0%') UPDATE "glyph" SET "aspect" = 60 * 24 * 24 WHERE "id" IN (SELECT "id" FROM "cte")"#
    /// );
    /// assert_eq!(
    ///     query.to_string(SqliteQueryBuilder),
    ///     r#"WITH "cte" ("id") AS (SELECT "id" FROM "glyph" WHERE "image" LIKE '0%') UPDATE "glyph" SET "aspect" = 60 * 24 * 24 WHERE "id" IN (SELECT "id" FROM "cte")"#
    /// );
    /// assert_eq!(
    ///     query.audit_unwrap().updated_tables(),
    ///     [Glyph::Table.into_iden()]
    /// );
    /// assert_eq!(
    ///     query.audit_unwrap().selected_tables(),
    ///     [Glyph::Table.into_iden()]
    /// );
    /// ```
    pub fn with_cte<C: Into<WithClause>>(&mut self, clause: C) -> &mut Self {
        self.with = Some(clause.into());
        self
    }

    /// Get column values
    pub fn get_values(&self) -> &[(DynIden, Box<Expr>)] {
        &self.values
    }
}

impl QueryStatementBuilder for UpdateStatement {
    fn build_collect_any_into(&self, query_builder: &impl QueryBuilder, sql: &mut impl SqlWriter) {
        query_builder.prepare_update_statement(self, sql);
    }
}

impl UpdateStatement {
    pub fn build_any(&self, query_builder: &impl QueryBuilder) -> (String, Values) {
        <Self as QueryStatementBuilder>::build_any(self, query_builder)
    }

    pub fn build_collect_any(
        &self,
        query_builder: &impl QueryBuilder,
        sql: &mut impl SqlWriter,
    ) -> String {
        <Self as QueryStatementBuilder>::build_collect_any(self, query_builder, sql)
    }

    pub fn build_collect_any_into(
        &self,
        query_builder: &impl QueryBuilder,
        sql: &mut impl SqlWriter,
    ) {
        <Self as QueryStatementBuilder>::build_collect_any_into(self, query_builder, sql)
    }
}

impl From<UpdateStatement> for QueryStatement {
    fn from(s: UpdateStatement) -> Self {
        Self::Update(s)
    }
}

impl From<UpdateStatement> for SubQueryStatement {
    fn from(s: UpdateStatement) -> Self {
        Self::UpdateStatement(s)
    }
}

impl QueryStatementWriter for UpdateStatement {
    fn build_collect_into<T: QueryBuilder>(&self, query_builder: T, sql: &mut impl SqlWriter) {
        query_builder.prepare_update_statement(self, sql);
    }
}

impl UpdateStatement {
    pub fn to_string<T: QueryBuilder>(&self, query_builder: T) -> String {
        <Self as QueryStatementWriter>::to_string(self, query_builder)
    }

    pub fn build<T: QueryBuilder>(&self, query_builder: T) -> (String, Values) {
        <Self as QueryStatementWriter>::build(self, query_builder)
    }

    pub fn build_collect<T: QueryBuilder>(
        &self,
        query_builder: T,
        sql: &mut impl SqlWriter,
    ) -> String {
        <Self as QueryStatementWriter>::build_collect(self, query_builder, sql)
    }

    pub fn build_collect_into<T: QueryBuilder>(&self, query_builder: T, sql: &mut impl SqlWriter) {
        <Self as QueryStatementWriter>::build_collect_into(self, query_builder, sql);
    }
}

impl OrderedStatement for UpdateStatement {
    fn add_order_by(&mut self, order: OrderExpr) -> &mut Self {
        self.orders.push(order);
        self
    }

    fn clear_order_by(&mut self) -> &mut Self {
        self.orders = Vec::new();
        self
    }
}

impl UpdateStatement {
    pub fn add_order_by(&mut self, order: OrderExpr) -> &mut Self {
        <Self as OrderedStatement>::add_order_by(self, order)
    }

    pub fn clear_order_by(&mut self) -> &mut Self {
        <Self as OrderedStatement>::clear_order_by(self)
    }

    pub fn order_by<T: IntoColumnRef>(&mut self, col: T, order: Order) -> &mut Self {
        <Self as OrderedStatement>::order_by(self, col, order)
    }

    pub fn order_by_expr(&mut self, expr: Expr, order: Order) -> &mut Self {
        <Self as OrderedStatement>::order_by_expr(self, expr, order)
    }

    pub fn order_by_customs<I, T>(&mut self, cols: I) -> &mut Self
    where
        T: Into<Cow<'static, str>>,
        I: IntoIterator<Item = (T, Order)>,
    {
        <Self as OrderedStatement>::order_by_customs(self, cols)
    }

    pub fn order_by_columns<I, T>(&mut self, cols: I) -> &mut Self
    where
        T: IntoColumnRef,
        I: IntoIterator<Item = (T, Order)>,
    {
        <Self as OrderedStatement>::order_by_columns(self, cols)
    }

    pub fn order_by_with_nulls<T: IntoColumnRef>(
        &mut self,
        col: T,
        order: Order,
        nulls: NullOrdering,
    ) -> &mut Self {
        <Self as OrderedStatement>::order_by_with_nulls(self, col, order, nulls)
    }

    pub fn order_by_expr_with_nulls(
        &mut self,
        expr: Expr,
        order: Order,
        nulls: NullOrdering,
    ) -> &mut Self {
        <Self as OrderedStatement>::order_by_expr_with_nulls(self, expr, order, nulls)
    }

    pub fn order_by_customs_with_nulls<I, T>(&mut self, cols: I) -> &mut Self
    where
        T: Into<Cow<'static, str>>,
        I: IntoIterator<Item = (T, Order, NullOrdering)>,
    {
        <Self as OrderedStatement>::order_by_customs_with_nulls(self, cols)
    }

    pub fn order_by_columns_with_nulls<I, T>(&mut self, cols: I) -> &mut Self
    where
        T: IntoColumnRef,
        I: IntoIterator<Item = (T, Order, NullOrdering)>,
    {
        <Self as OrderedStatement>::order_by_columns_with_nulls(self, cols)
    }
}

impl ConditionalStatement for UpdateStatement {
    fn and_or_where(&mut self, condition: LogicalChainOper) -> &mut Self {
        self.r#where.add_and_or(condition);
        self
    }

    fn cond_where<C>(&mut self, condition: C) -> &mut Self
    where
        C: IntoCondition,
    {
        self.r#where.add_condition(condition.into_condition());
        self
    }
}

impl UpdateStatement {
    pub fn and_where(&mut self, other: Expr) -> &mut Self {
        <Self as ConditionalStatement>::and_where(self, other)
    }

    pub fn and_where_option(&mut self, other: Option<Expr>) -> &mut Self {
        <Self as ConditionalStatement>::and_where_option(self, other)
    }

    pub fn and_or_where(&mut self, condition: LogicalChainOper) -> &mut Self {
        <Self as ConditionalStatement>::and_or_where(self, condition)
    }

    pub fn cond_where<C: IntoCondition>(&mut self, condition: C) -> &mut Self {
        <Self as ConditionalStatement>::cond_where(self, condition)
    }
}
