use super::MemoryConfigStore;
use crate::storage::Error;
use pbbson::Model;
use pbbson::bson::Bson;

pub async fn perform<B: Clone + Send + Sync + ToString>(
    this: &MemoryConfigStore<B>,
    bucket: B,
    model: Model,
) -> Result<Model, Error> {
    let doc_holder = this.doc_holder(&bucket)?;
    let mut model = model.clone();
    let model_id = match model.id() {
        Ok(id) => id,
        _ => {
            let id = xid::new().to_string();
            model.insert("id", id.clone());
            id
        }
    };
    let mut docs = doc_holder
        .docs
        .lock()
        .map_err(|e| Error::failed_precondition(e.to_string()))?;

    // Store
    docs.insert(model_id.clone(), model.clone());

    // Maintain belongs-to fk's
    for belongs_to in this
        .belongs_tos_by_bucket
        .get(&bucket.to_string())
        .cloned()
        .unwrap_or_default()
    {
        let local_value = model.get(&belongs_to.local);
        match local_value {
            Some(Bson::String(id)) => {
                match belongs_to.inverse.clone() {
                    None => {
                        // TODO validate fk only
                    }
                    Some(inverse) => {
                        // Update foreign collection to reference this
                        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,
                        };
                        match foreign_model.get_mut(&inverse) {
                            Some(foreign_field) => {
                                if let Bson::Array(array) = foreign_field {
                                    array.push(Bson::String(model_id.clone()));
                                }
                            }
                            None => {
                                let array = vec![Bson::String(model_id.clone())];
                                foreign_model.insert(inverse.clone(), Bson::Array(array));
                            }
                        }
                    }
                }
            }
            _ => {
                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 = model.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 reference 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)) => array.push(Bson::String(model_id.clone())),
                                    _ => {
                                        foreign_model.insert(inverse.clone(), Bson::String(model_id.clone()));
                                    }
                                };
                            }
                        }
                    }
                }
            }
            _ => {
                log::error!("Unknown fk type: {local_value:?}");
            }
        }
    }

    Ok(model)
}
