use crate::storage::associations::{BelongsTo, HasMany};
use crate::storage::{config_store::ConfigStore, Error};
use async_trait::async_trait;
use bson::Bson;
use log::error;
use pbbson::Model;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

struct DocHolder {
    docs: Mutex<HashMap<String, Model>>,
}

#[allow(unused)]
pub struct MemoryConfigStore<B: Clone + Send + Sync + ToString> {
    belongs_tos_by_bucket: HashMap<String, Vec<BelongsTo<B>>>,
    doc_holders: Mutex<HashMap<String, Arc<DocHolder>>>,
    has_manys_by_bucket: HashMap<String, Vec<HasMany<B>>>,
}

impl<B: Clone + Send + Sync + ToString> MemoryConfigStore<B> {
    #[allow(unused)]
    pub fn new(
        _app_name: &str,
        belongs_tos_by_bucket: HashMap<String, Vec<BelongsTo<B>>>,
        has_manys_by_bucket: HashMap<String, Vec<HasMany<B>>>,
    ) -> Self {
        let doc_holders = Mutex::new(HashMap::new());
        Self {
            belongs_tos_by_bucket,
            doc_holders,
            has_manys_by_bucket,
        }
    }
}

#[async_trait]
impl<B: Clone + Send + Sync + ToString> ConfigStore<B> for MemoryConfigStore<B> {
    async fn create(&self, bucket: B, model: Model) -> Result<Model, Error> {
        let doc_holder = self.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 self
            .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 = self.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));
                                }
                            }
                        }
                    }
                }
                _ => {
                    error!("Unknown fk type: {local_value:?}");
                }
            }
        }

        // Maintain has-many fk's
        for has_many in self
            .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 = self.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()));
                                        }
                                    };
                                }
                            }
                        }
                    }
                }
                _ => {
                    error!("Unknown fk type: {local_value:?}");
                }
            }
        }

        Ok(model)
    }

    async fn delete(&self, bucket: B, id: &str, by_account_id: Option<String>) -> Result<(), Error> {
        let doc_holder = self.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 self
            .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 = self.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()));
                            }
                        }
                        _ => {
                            error!("Unknown fk type: {local_value:?}");
                        }
                    }
                }
            }
        }

        // Maintain has-many fk's
        for has_many in self
            .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 = self.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);
                                        }
                                    };
                                }
                            }
                        }
                    }
                }
                _ => {
                    error!("Unknown fk type: {local_value:?}");
                }
            }
        }

        Ok(())
    }

    async fn find(&self, bucket: B, id: &str) -> Result<Model, Error> {
        let doc_holder = self.doc_holder(&bucket)?;
        let docs = doc_holder
            .docs
            .lock()
            .map_err(|e| Error::failed_precondition(e.to_string()))?;
        let model = match docs.get(id) {
            None => return Err(Error::not_found("No such record")),
            Some(model) => model,
        };
        if model.contains_field("deletedAt") {
            return Err(Error::not_found("No such record"));
        }
        Ok(model.clone())
    }

    async fn find_many(&self, bucket: B, _filter: Model) -> Result<Vec<Model>, Error> {
        let doc_holder = self.doc_holder(&bucket)?;
        let docs = doc_holder
            .docs
            .lock()
            .map_err(|e| Error::failed_precondition(e.to_string()))?;
        Ok(docs.values().cloned().collect())
    }

    async fn update(&self, bucket: B, model: Model) -> Result<Model, Error> {
        let model_id = model.id().map_err(|e| Error::internal(e.to_string()))?;
        let doc_holder = self.doc_holder(&bucket)?;
        let mut docs = doc_holder
            .docs
            .lock()
            .map_err(|e| Error::failed_precondition(e.to_string()))?;
        let existing = match docs.get(&model_id) {
            None => return Err(Error::not_found("No such record")),
            Some(existing) => existing.clone(),
        };

        // Store the update
        docs.insert(model.id().map_err(|e| Error::internal(e.to_string()))?, model.clone());

        // Maintain fk's of dereferenced belongs-tos
        let belongs_tos = self
            .belongs_tos_by_bucket
            .get(&bucket.to_string())
            .cloned()
            .unwrap_or_default();
        for belongs_to in belongs_tos.iter() {
            let existing_fk = existing.get(&belongs_to.local);
            let new_fk = model.get(&belongs_to.local).cloned();
            match existing_fk {
                None => continue,
                Some(existing_fk_value) => {
                    if existing_fk == new_fk.as_ref() {
                        continue;
                    }
                    let existing_fk_id = match existing_fk_value.as_str() {
                        Some(existing_fk_id) => existing_fk_id,
                        None => continue,
                    };
                    let remote_bucket = self.doc_holder(&belongs_to.remote)?;
                    let mut remote_docs = remote_bucket
                        .docs
                        .lock()
                        .map_err(|e| Error::failed_precondition(e.to_string()))?;
                    let remote_model = match remote_docs.get(existing_fk_id) {
                        None => continue,
                        Some(remote_model) => remote_model,
                    };
                    if let Some(inverse) = belongs_to.inverse.as_ref() {
                        let remote_fk_value = remote_model.get(inverse.clone());
                        if let Some(Bson::Array(array)) = remote_fk_value {
                            let array: Vec<_> = array
                                .iter()
                                .filter(|remote_fk| *remote_fk != &Bson::String(model_id.clone()))
                                .collect();
                            let mut remote_model = remote_model.clone();
                            remote_model.insert(inverse.clone(), array);
                            remote_docs.insert(existing_fk_id.to_string(), remote_model);
                        }
                    }
                }
            }
        }

        // Maintain fk's of newly referenced belongs-tos
        for belongs_to in belongs_tos.iter() {
            let existing_fk = existing.get(&belongs_to.local);
            let new_fk = model.get(&belongs_to.local);
            match new_fk {
                None => continue,
                Some(new_fk_value) => {
                    if existing_fk == new_fk {
                        continue;
                    }
                    let new_fk_object_id = new_fk_value.as_str().unwrap();
                    let remote_bucket = self.doc_holder(&belongs_to.remote)?;
                    let mut remote_docs = remote_bucket
                        .docs
                        .lock()
                        .map_err(|e| Error::failed_precondition(e.to_string()))?;
                    let remote_model = match remote_docs.get(new_fk_object_id) {
                        None => continue,
                        Some(remote_model) => remote_model,
                    };
                    if let Some(inverse) = belongs_to.inverse.as_ref() {
                        let remote_fk_value = remote_model.get(inverse.clone());
                        if let Some(Bson::Array(array)) = remote_fk_value {
                            let mut array = array.clone();
                            array.push(Bson::String(model_id.clone()));
                            let mut remote_model = remote_model.clone();
                            remote_model.insert(inverse.clone(), array);
                            remote_docs.insert(new_fk_object_id.to_string(), remote_model);
                        }
                    }
                }
            }
        }

        // Maintain has-many fk's
        for has_many in self
            .has_manys_by_bucket
            .get(&bucket.to_string())
            .cloned()
            .unwrap_or_default()
        {
            let existing_local_value = existing.get(&has_many.local);
            let local_value = model.get(&has_many.local);
            match (existing_local_value, local_value) {
                (Some(Bson::Array(existing_array)), Some(Bson::Array(array))) => {
                    match has_many.inverse.clone() {
                        None => {
                            // TODO validate fk only
                        }
                        Some(inverse) => {
                            let remote_bucket = self.doc_holder(&has_many.remote)?;
                            let mut remote_docs = remote_bucket
                                .docs
                                .lock()
                                .map_err(|e| Error::failed_precondition(e.to_string()))?;
                            // Maintain dereferenced fks
                            for existing_id in existing_array.iter() {
                                if array.contains(existing_id) {
                                    continue;
                                }
                                if let Bson::String(id) = existing_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);
                                        }
                                    };
                                }
                            }

                            // Maintain newly referenced fks
                            for id in array.iter() {
                                if existing_array.contains(id) {
                                    continue;
                                }
                                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.push(index of model_id))
                                        }
                                        _ => {
                                            foreign_model.insert(&inverse, Bson::String(model_id.clone()));
                                        }
                                    };
                                }
                            }
                        }
                    }
                }
                _ => {
                    error!("Unknown fk type: {local_value:?}");
                }
            }
        }

        Ok(model)
    }
}

impl<B: Clone + Send + Sync + ToString> MemoryConfigStore<B> {
    fn doc_holder(&self, bucket: &B) -> Result<Arc<DocHolder>, Error> {
        let mut doc_holders = self
            .doc_holders
            .lock()
            .map_err(|e| Error::failed_precondition(e.to_string()))?;
        match doc_holders.get(&bucket.to_string()) {
            Some(doc_holder) => Ok(doc_holder.clone()),
            None => {
                let doc_holder = Arc::new(DocHolder {
                    docs: Default::default(),
                });
                doc_holders.insert(bucket.to_string(), doc_holder.clone());
                Ok(doc_holder)
            }
        }
    }
}
