use super::{
    MongodbConfigStore,
    serde::{now_as_datetime, try_to_object_id},
};
use crate::storage::Error;
use mongodb::{ClientSession, Collection, bson::Document, results::UpdateResult};
use pbbson::bson::{Bson, doc};

pub async fn delete<B: Clone + Send + Sync + ToString>(
    this: &MongodbConfigStore<B>,
    mut session: Option<&mut ClientSession>,
    bucket: B,
    id: &str,
    by_account_id: Option<String>,
) -> Result<(), Error> {
    let id = try_to_object_id(id)?;
    let coll: Collection<Document> = this.db.collection(&bucket.to_string());
    let filter = doc! { "_id": &id };
    let now = now_as_datetime();
    let mut set = doc! {"deletedAt": now};
    if let Some(ref account_id) = by_account_id {
        let deleted_by_account_id = try_to_object_id(account_id)?;
        set.insert("deletedByAccountId", deleted_by_account_id);
    }

    // Load existing
    let existing = this.find_in_session(&mut session, bucket.clone(), id).await?;

    // Soft delete
    let update = doc! {"$set": set};
    let res: UpdateResult = {
        let op = coll.update_one(filter, update);
        if let Some(ref mut session) = session {
            op.session(&mut **session).await?
        } else {
            op.await?
        }
    };
    if res.matched_count == 0 {
        return Err(Error::not_found("No such record"));
    }

    // Maintain belongs-to fk's
    for belongs_to in this
        .belongs_tos_by_bucket
        .get(&bucket.to_string())
        .cloned()
        .unwrap_or_default()
    {
        match belongs_to.inverse {
            None => continue,
            Some(inverse) => {
                let local_value = existing.get(&belongs_to.local);
                match local_value {
                    Some(Bson::ObjectId(id_)) => {
                        let pull = doc! {&inverse: id};
                        let mut set = doc! {"updatedAt": now};
                        if let Some(ref account_id) = by_account_id {
                            let updated_by_account_id = try_to_object_id(account_id)?;
                            set.insert("updatedByAccountId", updated_by_account_id);
                        }
                        let update = doc! {"$pull": pull, "$set": set};
                        let remote_coll: Collection<Document> = this.db.collection(&belongs_to.remote.to_string());
                        let filter = doc! { "_id": id_ };
                        let res: UpdateResult = {
                            let op = remote_coll.update_one(filter, update);
                            if let Some(ref mut session) = session {
                                op.session(&mut **session).await?
                            } else {
                                op.await?
                            }
                        };
                        if res.matched_count == 0 {
                            //error!("Could not find _ to add _ fk")
                        }
                    }
                    _ => {
                        log::error!("Unknown fk type: {local_value:?}");
                    }
                }
            }
        }
    }

    // Maintain has-many fk's
    for has_many in this
        .has_manys_by_bucket
        .get(&bucket.to_string())
        .cloned()
        .unwrap_or_default()
    {
        let local_value = existing.get(&has_many.local);
        match local_value {
            Some(Bson::Array(array)) => {
                match has_many.inverse {
                    None => {
                        // TODO validate fk only
                    }
                    Some(inverse) => {
                        // Update foreign bucket to dereference this
                        let remote_coll: Collection<Document> = this.db.collection(&has_many.remote.to_string());
                        for id in array.iter() {
                            if let Bson::String(id_) = id {
                                let mut set = doc! {&inverse: Bson::Null, "updatedAt": now};
                                if let Some(ref account_id) = by_account_id {
                                    let updated_by_account_id = try_to_object_id(account_id)?;
                                    set.insert("updatedByAccountId", updated_by_account_id);
                                }
                                //let unset = doc! {&inverse: 1};
                                let update = doc! {"$set": set};
                                let filter = doc! { "_id": try_to_object_id(id_)? };
                                let res: UpdateResult = {
                                    let op = remote_coll.update_one(filter, update);
                                    if let Some(ref mut session) = session {
                                        op.session(&mut **session).await?
                                    } else {
                                        op.await?
                                    }
                                };
                                if res.matched_count == 0 {
                                    //error!("Could not find _ to add _ fk")
                                }
                            }
                        }
                    }
                }
            }
            _ => {
                log::error!("Unknown fk type: {local_value:?}");
            }
        }
    }

    Ok(())
}
