use crate::{
    db_connectors::{BotVersion, DbBot},
    EngineError,
};
use bson::{doc, Bson};
use chrono::SecondsFormat;
use csml_interpreter::data::csml_bot::SerializeCsmlBot;

fn format_bot_struct(bot: bson::ordered::OrderedDocument) -> Result<DbBot, EngineError> {
    Ok(DbBot {
        id: bot.get_object_id("_id").unwrap().to_hex(),
        bot_id: bot.get_str("bot_id").unwrap().to_owned(),
        bot: bot.get_str("bot").unwrap().to_owned(),
        engine_version: bot.get_str("engine_version").unwrap().to_owned(),
        created_at: bot
            .get_utc_datetime("created_at")
            .unwrap()
            .to_rfc3339_opts(SecondsFormat::Millis, true),
    })
}

pub fn create_bot_version(
    bot_id: String,
    bot: String,
    db: &mongodb::Database,
) -> Result<String, EngineError> {
    let collection = db.collection("bot");
    let time = Bson::UtcDatetime(chrono::Utc::now());

    let bot = doc! {
        "bot_id": bot_id,
        "bot": bot,
        "engine_version": env!("CARGO_PKG_VERSION").to_owned(),
        "created_at": &time
    };

    let inserted = collection.insert_one(bot.clone(), None)?;

    let id = inserted.inserted_id.as_object_id().unwrap();

    Ok(id.to_hex())
}

pub fn get_bot_versions(
    bot_id: &str,
    limit: Option<i64>,
    pagination_key: Option<String>,
    db: &mongodb::Database,
) -> Result<serde_json::Value, EngineError> {
    let collection = db.collection("bot");

    let limit = match limit {
        Some(limit) if limit >= 1 => limit + 1,
        Some(_limit) => 21,
        None => 21,
    };

    let filter = match pagination_key {
        Some(key) => {
            let base64decoded = match base64::decode(&key) {
                Ok(base64decoded) => base64decoded,
                Err(_) => return Err(EngineError::Manager(format!("Invalid pagination_key"))),
            };

            let key: String = match serde_json::from_slice(&base64decoded) {
                Ok(key) => key,
                Err(_) => return Err(EngineError::Manager(format!("Invalid pagination_key"))),
            };

            doc! {
                "bot_id": bot_id,
                "_id": {"$gt": bson::oid::ObjectId::with_string(&key).unwrap() }
            }
        }
        None => doc! {"bot_id": bot_id },
    };

    let find_options = mongodb::options::FindOptions::builder()
        .sort(doc! { "$natural": -1, })
        .batch_size(30)
        .limit(limit)
        .build();

    let cursor = collection.find(filter, find_options)?;
    let mut bots = vec![];

    for doc in cursor {
        match doc {
            Ok(bot_doc) => {
                let bot_version = format_bot_struct(bot_doc)?;

                let base64decoded = base64::decode(&bot_version.bot).unwrap();
                let csml_bot: SerializeCsmlBot = bincode::deserialize(&base64decoded[..]).unwrap();

                let mut json = serde_json::json!({
                    "version_id": bot_version.id,
                    "id": csml_bot.id,
                    "name": csml_bot.name,
                    "default_flow": csml_bot.default_flow,
                    "engine_version": bot_version.engine_version,
                    "created_at": bot_version.created_at
                });

                if let Some(custom_components) = csml_bot.custom_components {
                    json["custom_components"] = serde_json::json!(custom_components);
                }

                bots.push(json);
            }
            Err(_) => (),
        };
    }

    match bots.len() == limit as usize {
        true => {
            bots.pop();
            match bots.last() {
                Some(last) => {
                    let pagination_key = base64::encode(last["version_id"].clone().to_string());

                    Ok(serde_json::json!({"bots": bots, "pagination_key": pagination_key}))
                }
                None => Ok(serde_json::json!({ "bots": bots })),
            }
        }
        false => Ok(serde_json::json!({ "bots": bots })),
    }
}

pub fn get_bot_by_version_id(
    id: &str,
    db: &mongodb::Database,
) -> Result<Option<BotVersion>, EngineError> {
    let collection = db.collection("bot");

    let filter = doc! {
        "_id": bson::oid::ObjectId::with_string(id).unwrap()
    };

    let find_options = mongodb::options::FindOneOptions::builder()
        .sort(doc! { "$natural": -1, })
        .build();

    let result = collection.find_one(filter, find_options)?;

    match result {
        Some(bot) => {
            let bot = format_bot_struct(bot)?;

            let base64decoded = base64::decode(&bot.bot).unwrap();
            let csml_bot: SerializeCsmlBot = bincode::deserialize(&base64decoded[..]).unwrap();

            Ok(Some(BotVersion {
                bot: csml_bot.to_bot(),
                version_id: bot.id,
                engine_version: env!("CARGO_PKG_VERSION").to_owned(),
            }))
        }
        None => Ok(None),
    }
}

pub fn get_last_bot_version(
    bot_id: &str,
    db: &mongodb::Database,
) -> Result<Option<BotVersion>, EngineError> {
    let collection = db.collection("bot");

    let filter = doc! {
        "bot_id": bot_id,
    };

    let find_options = mongodb::options::FindOneOptions::builder()
        .sort(doc! { "$natural": -1,  })
        .build();

    let result = collection.find_one(filter, find_options)?;

    match result {
        Some(bot) => {
            let bot = format_bot_struct(bot)?;

            let base64decoded = base64::decode(&bot.bot).unwrap();
            let csml_bot: SerializeCsmlBot = bincode::deserialize(&base64decoded[..]).unwrap();

            Ok(Some(BotVersion {
                bot: csml_bot.to_bot(),
                version_id: bot.id,
                engine_version: env!("CARGO_PKG_VERSION").to_owned(),
            }))
        }
        None => Ok(None),
    }
}

pub fn delete_bot_version(version_id: &str, db: &mongodb::Database) -> Result<(), EngineError> {
    let collection = db.collection("bot");

    let filter = doc! {
        "_id": bson::oid::ObjectId::with_string(version_id).unwrap()
    };

    collection.delete_one(filter, None)?;

    Ok(())
}

pub fn delete_bot_versions(bot_id: &str, db: &mongodb::Database) -> Result<(), EngineError> {
    let collection = db.collection("bot");

    let filter = doc! {
        "bot_id": bot_id,
    };

    collection.delete_many(filter, None)?;

    Ok(())
}
