use crate::storage::Error;
use bson::Bson;
use mongodb::bson::{oid::ObjectId, DateTime};
use pbbson::Model;
use std::str::FromStr;
use std::time::SystemTime;

pub(crate) fn now_as_datetime() -> DateTime {
    let now = SystemTime::now();
    DateTime::from_system_time(now)
}

pub(crate) fn try_to_object_id(id: &str) -> Result<ObjectId, Error> {
    const WHAT: &str = "IDs";
    require_xid_base32hex(WHAT, id)?;
    let as_xid = match xid::Id::from_str(id) {
        Ok(xid) => xid,
        Err(e) => {
            let msg = format!("{WHAT} must be 12-byte XID's in 20-byte base32hex encoded form ({e:?})");
            return Err(Error::invalid_argument(msg));
        }
    };
    Ok(ObjectId::from_bytes(*as_xid.as_bytes()))
}

pub(crate) fn require_xid_base32hex(what: &str, id: &str) -> Result<(), Error> {
    if id.len() != 20 {
        return Err(Error::invalid_argument(format!(
            "{what} must be 12-byte XID's in 20-byte base32hex encoded form"
        )));
    }
    Ok(())
}

pub(crate) fn transform_to_mongo(model: Model) -> Result<Model, Error> {
    let mut local_model = Model::default();
    let local_model_id = match model.get("id") {
        Some(Bson::ObjectId(object_id)) => Some(*object_id),
        Some(Bson::String(id)) => Some(try_to_object_id(id)?),
        _ => None,
    };
    if let Some(local_model_id) = local_model_id {
        local_model.insert("_id", local_model_id);
    }
    for (k, v) in model.iter().filter(|(k, _v)| *k != "id") {
        if k.ends_with("Id") {
            local_model.insert(
                k,
                match v {
                    Bson::ObjectId(object_id) => Some(*object_id),
                    Bson::String(id) => Some(try_to_object_id(id).map_err(|e| Error::internal(e.to_string()))?),
                    _ => return Err(Error::internal("Unsupported foreign key type")),
                },
            );
        } else {
            local_model.insert(k, v);
        }
    }
    Ok(local_model)
}

pub(crate) fn transform_from_mongo(model: Model) -> Result<Model, Error> {
    let mut model = model.clone();
    if let Some(Bson::ObjectId(object_id)) = model.get("_id") {
        let id = xid::Id::from_bytes(&object_id.bytes()).map_err(|e| Error::internal(e.to_string()))?;
        model.insert("id".to_string(), id.to_string());
        model.remove("_id");
    }
    Ok(model)
}
