use super::{ReturningSelector, SelectModel};
use crate::{
    ColumnTrait, ConnectionTrait, DeleteMany, DeleteOne, EntityTrait, Iterable, ValidatedDeleteOne,
    error::*,
};
use sea_query::{DeleteStatement, Query};

/// Lower-level executor that runs a raw `sea_query` [`DeleteStatement`] and
/// collects the result. Most code shouldn't need it directly — prefer
/// [`EntityTrait::delete`](crate::EntityTrait::delete) /
/// [`delete_many`](crate::EntityTrait::delete_many) /
/// [`delete_by_id`](crate::EntityTrait::delete_by_id), which return
/// strongly-typed builders.
#[derive(Clone, Debug)]
pub struct Deleter {
    query: DeleteStatement,
}

/// Result of a `DELETE`: how many rows were removed.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct DeleteResult {
    /// Number of rows removed by the statement.
    pub rows_affected: u64,
}

impl<E> ValidatedDeleteOne<E>
where
    E: EntityTrait,
{
    /// Execute a DELETE operation on one ActiveModel
    pub fn exec<C>(self, db: &C) -> Result<DeleteResult, DbErr>
    where
        C: ConnectionTrait,
    {
        exec_delete_only(self.query, db)
    }

    /// Execute an delete operation and return the deleted model
    pub fn exec_with_returning<C>(self, db: &C) -> Result<Option<E::Model>, DbErr>
    where
        C: ConnectionTrait,
    {
        exec_delete_with_returning_one::<E, _>(self.query, db)
    }
}

impl<E> DeleteOne<E>
where
    E: EntityTrait,
{
    /// Execute a DELETE operation on one ActiveModel
    pub fn exec<C>(self, db: &C) -> Result<DeleteResult, DbErr>
    where
        C: ConnectionTrait,
    {
        self.0?.exec(db)
    }

    /// Execute an delete operation and return the deleted model
    pub fn exec_with_returning<C>(self, db: &C) -> Result<Option<E::Model>, DbErr>
    where
        C: ConnectionTrait,
    {
        self.0?.exec_with_returning(db)
    }
}

impl<'a, E> DeleteMany<E>
where
    E: EntityTrait,
{
    /// Execute a DELETE operation on many ActiveModels
    pub fn exec<C>(self, db: &'a C) -> Result<DeleteResult, DbErr>
    where
        C: ConnectionTrait,
    {
        exec_delete_only(self.query, db)
    }

    /// Execute an delete operation and return the deleted model
    pub fn exec_with_returning<C>(self, db: &C) -> Result<Vec<E::Model>, DbErr>
    where
        E: EntityTrait,
        C: ConnectionTrait,
    {
        exec_delete_with_returning_many::<E, _>(self.query, db)
    }
}

impl Deleter {
    /// Instantiate a new [Deleter] by passing it a [DeleteStatement]
    pub fn new(query: DeleteStatement) -> Self {
        Self { query }
    }

    /// Execute a DELETE operation
    pub fn exec<C>(self, db: &C) -> Result<DeleteResult, DbErr>
    where
        C: ConnectionTrait,
    {
        exec_delete(self.query, db)
    }

    /// Execute an delete operation and return the deleted model
    pub fn exec_with_returning<E, C>(self, db: &C) -> Result<Vec<E::Model>, DbErr>
    where
        E: EntityTrait,
        C: ConnectionTrait,
    {
        exec_delete_with_returning_many::<E, _>(self.query, db)
    }
}

impl DeleteResult {
    #[doc(hidden)]
    pub fn empty() -> Self {
        Self { rows_affected: 0 }
    }

    #[doc(hidden)]
    pub fn merge(&mut self, other: DeleteResult) {
        self.rows_affected += other.rows_affected;
    }
}

fn exec_delete_only<C>(query: DeleteStatement, db: &C) -> Result<DeleteResult, DbErr>
where
    C: ConnectionTrait,
{
    Deleter::new(query).exec(db)
}

fn exec_delete<C>(query: DeleteStatement, db: &C) -> Result<DeleteResult, DbErr>
where
    C: ConnectionTrait,
{
    let result = db.execute(&query)?;
    Ok(DeleteResult {
        rows_affected: result.rows_affected(),
    })
}

fn exec_delete_with_returning_one<E, C>(
    mut query: DeleteStatement,
    db: &C,
) -> Result<Option<E::Model>, DbErr>
where
    E: EntityTrait,
    C: ConnectionTrait,
{
    let db_backend = db.get_database_backend();
    match db.support_returning() {
        true => {
            let returning = Query::returning().exprs(
                E::Column::iter().map(|c| c.select_enum_as(c.into_returning_expr(db_backend))),
            );
            query.returning(returning);
            ReturningSelector::<SelectModel<<E>::Model>, _>::from_query(query).one(db)
        }
        false => Err(DbErr::BackendNotSupported {
            db: db_backend.as_str(),
            ctx: "DELETE RETURNING",
        }),
    }
}

fn exec_delete_with_returning_many<E, C>(
    mut query: DeleteStatement,
    db: &C,
) -> Result<Vec<E::Model>, DbErr>
where
    E: EntityTrait,
    C: ConnectionTrait,
{
    let db_backend = db.get_database_backend();
    match db.support_returning() {
        true => {
            let returning = Query::returning().exprs(
                E::Column::iter().map(|c| c.select_enum_as(c.into_returning_expr(db_backend))),
            );
            query.returning(returning);
            ReturningSelector::<SelectModel<<E>::Model>, _>::from_query(query).all(db)
        }
        false => Err(DbErr::BackendNotSupported {
            db: db_backend.as_str(),
            ctx: "DELETE RETURNING",
        }),
    }
}

#[cfg(test)]
mod tests {
    use crate::tests_cfg::cake;

    #[test]
    fn delete_error() {
        use crate::{DbBackend, DbErr, Delete, EntityTrait, MockDatabase};

        let db = MockDatabase::new(DbBackend::MySql).into_connection();

        assert!(matches!(
            Delete::one(cake::ActiveModel {
                ..Default::default()
            })
            .exec(&db),
            Err(DbErr::PrimaryKeyNotSet { .. })
        ));

        assert!(matches!(
            cake::Entity::delete(cake::ActiveModel::default()).exec(&db),
            Err(DbErr::PrimaryKeyNotSet { .. })
        ));
    }
}
