use super::MemoryConfigStore;
use crate::storage::Error;
use pbbson::bson::{self, Bson};

pub(crate) async fn perform<B: Clone + Send + Sync + ToString>(
    this: &MemoryConfigStore<B>,
    bucket: B,
    id: &str,
    by_account_id: Option<String>,
) -> Result<(), Error> {
    let doc_holder = this.doc_holder(&bucket)?;
    let mut docs = doc_holder
        .docs
        .lock()
        .map_err(|e| Error::failed_precondition(e.to_string()))?;
    let existing = match docs.get_mut(id) {
        None => return Err(Error::not_found("No such record")),
        Some(existing) => existing,
    };

    // Soft Delete
    existing.insert("deletedAt", Bson::DateTime(bson::DateTime::now()));
    if let Some(by_account_id) = by_account_id {
        existing.insert("deletedBy", Bson::String(by_account_id));
    }

    // 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::String(id_)) => {
                        let remote_bucket = this.doc_holder(&belongs_to.remote)?;
                        let mut remote_docs = remote_bucket
                            .docs
                            .lock()
                            .map_err(|e| Error::failed_precondition(e.to_string()))?;
                        let foreign_model = match remote_docs.get_mut(id_) {
                            None => continue,
                            Some(foreign_model) => foreign_model,
                        };
                        if let Some(Bson::Array(array)) = foreign_model.get_mut(&inverse) {
                            array.retain(|field| field != &Bson::String(id.to_string()));
                        }
                    }
                    _ => {
                        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.clone() {
                    None => {
                        // TODO validate fk only
                    }
                    Some(inverse) => {
                        // Update foreign collection to dereference this
                        let remote_bucket = this.doc_holder(&has_many.remote)?;
                        let mut remote_docs = remote_bucket
                            .docs
                            .lock()
                            .map_err(|e| Error::failed_precondition(e.to_string()))?;
                        for id in array.iter() {
                            if let Bson::String(id) = id {
                                let foreign_model = match remote_docs.get_mut(id.as_str()) {
                                    None => {
                                        continue;
                                    }
                                    Some(foreign_model) => foreign_model,
                                };
                                match foreign_model.get_mut(&inverse) {
                                    Some(Bson::Array(_array)) => {
                                        // TODO array.delete(index of model_id))
                                    }
                                    _ => {
                                        foreign_model.remove(&inverse);
                                    }
                                };
                            }
                        }
                    }
                }
            }
            _ => {
                log::error!("Unknown fk type: {local_value:?}");
            }
        }
    }

    Ok(())
}
