use pbbson::Model;
use pbbson::bson::{Bson, doc};

pub fn find(model: &Model, metadata_field: &str, provider_key: &str, key: &str) -> Option<Bson> {
    if let Some(metadata_untyped) = model.get(metadata_field)
        && let Some(metadata) = metadata_untyped.as_document()
        && let Ok(provider_metadata) = metadata.get_document(provider_key)
        && let Some(value) = provider_metadata.get(key)
    {
        return Some(value.clone());
    }
    None
}

pub fn find_string(model: &Model, metadata_field: &str, provider_key: &str, key: &str) -> Option<String> {
    if let Some(maybe_string) = find(model, metadata_field, provider_key, key)
        && let Some(s) = maybe_string.as_str()
    {
        return Some(s.to_string());
    }
    None
}

pub fn set(model: &mut Model, metadata_field: &str, provider_key: &str, key: &str, value: Bson) {
    let metadata = match model.get_mut(metadata_field) {
        Some(Bson::Document(metadata)) => metadata,
        _ => {
            let metadata = doc! {
                provider_key: doc! {
                    key: value,
                },
            };
            model.insert(metadata_field, &metadata);
            return;
        }
    };
    match metadata.get_document_mut(provider_key) {
        Ok(provider_metadata) => {
            provider_metadata.insert(key, value);
        }
        Err(_) => {
            let provider_metadata = doc! {
                key: value,
            };
            metadata.insert(provider_key.to_string(), provider_metadata);
        }
    };
}

#[cfg(test)]
mod tests {
    use pbbson::Model;
    use pbbson::bson::doc;

    #[test]
    fn cannot_find_non_existent_metadata_field() {
        let model = Model::from(doc! {});
        assert!(super::find(&model, "metadata", "acme", "key").is_none());
    }

    #[test]
    fn cannot_find_non_existent_provider_field() {
        let model = Model::from(doc! {
            "metadata": doc! {}
        });
        assert!(super::find(&model, "metadata", "acme", "key").is_none());
    }

    #[test]
    fn cannot_find_non_existent_key_field() {
        let model = Model::from(doc! {
            "metadata": doc! {
                "acme": doc! {}
            }
        });
        assert!(super::find(&model, "metadata", "acme", "key").is_none());
    }

    #[test]
    fn can_find_string() {
        let model = Model::from(doc! {
            "metadata": doc! {
                "acme": doc! {
                    "abc": "def",
                }
            }
        });
        assert_eq!(
            super::find_string(&model, "metadata", "acme", "abc"),
            Some("def".to_string())
        );
    }

    #[test]
    fn can_set_when_metadata_field_doesnt_exist() {
        let mut model = Model::from(doc! {});
        super::set(&mut model, "metadata", "acme", "abc", "def".into());
        assert_eq!(
            super::find_string(&model, "metadata", "acme", "abc"),
            Some("def".to_string())
        );
    }

    #[test]
    fn can_set_when_provider_field_doesnt_exist() {
        let mut model = Model::from(doc! {
            "metadata": doc! {},
        });
        super::set(&mut model, "metadata", "acme", "abc", "def".into());
        assert_eq!(
            super::find_string(&model, "metadata", "acme", "abc"),
            Some("def".to_string())
        );
    }

    #[test]
    fn can_set_when_key_field_doesnt_exist() {
        let mut model = Model::from(doc! {
            "metadata": doc! {
                "acme": doc! {},
            }
        });
        super::set(&mut model, "metadata", "acme", "abc", "def".into());
        assert_eq!(
            super::find_string(&model, "metadata", "acme", "abc"),
            Some("def".to_string())
        );
    }

    #[test]
    fn can_set_when_key_exists() {
        let mut model = Model::from(doc! {
            "metadata": doc! {
                "acme": doc! {
                    "abc": "123",
                },
            }
        });
        super::set(&mut model, "metadata", "acme", "abc", "def".into());
        assert_eq!(
            super::find_string(&model, "metadata", "acme", "abc"),
            Some("def".to_string())
        );
    }
}
