use super::super::get_collection;

use log::info;
use mongodb::options::FindOptions;
use serde::{Deserialize, Serialize};
use crate::rocket::futures::StreamExt;
use mongodb::bson::{doc, from_bson, Bson};

#[derive(Serialize, Deserialize)]
struct MigrationInfo {
    _id: i32,
    revision: i32,
}

pub const LATEST_REVISION: i32 = 2;

pub async fn migrate_database() {
    let migrations = get_collection("migrations");
    let data = migrations
        .find_one(None, None)
        .await
        .expect("Failed to fetch migration data.");

    if let Some(doc) = data {
        let info: MigrationInfo =
            from_bson(Bson::Document(doc)).expect("Failed to read migration information.");

        let revision = run_migrations(info.revision).await;

        migrations
            .update_one(
                doc! {
                    "_id": info._id
                },
                doc! {
                    "$set": {
                        "revision": revision
                    }
                },
                None,
            )
            .await
            .expect("Failed to commit migration information.");

        info!("Migration complete. Currently at revision {}.", revision);
    } else {
        panic!("Database was configured incorrectly, possibly because initalization failed.")
    }
}

pub async fn run_migrations(revision: i32) -> i32 {
    info!("Starting database migration.");

    if revision <= 0 {
        info!("Running migration [revision 0]: Test migration system.");
    }

    if revision <= 1 {
        info!("Running migration [revision 1]: Add channels to guild object.");

        let col = get_collection("guilds");
        let mut guilds = col
            .find(
                None,
                FindOptions::builder().projection(doc! { "_id": 1 }).build(),
            )
            .await
            .expect("Failed to fetch guilds.");

        let mut result = get_collection("channels")
            .find(
                doc! {
                    "type": 2
                },
                FindOptions::builder()
                    .projection(doc! { "_id": 1, "guild": 1 })
                    .build(),
            )
            .await
            .expect("Failed to fetch channels.");

        let mut channels = vec![];
        while let Some(doc) = result.next().await {
            let channel = doc.expect("Failed to fetch channel.");
            let id = channel
                .get_str("_id")
                .expect("Failed to get channel id.")
                .to_string();
            
            let gid = channel
                .get_str("guild")
                .expect("Failed to get guild id.")
                .to_string();

            channels.push((id, gid));
        }

        while let Some(doc) = guilds.next().await {
            let guild = doc.expect("Failed to fetch guild.");
            let id = guild.get_str("_id").expect("Failed to get guild id.");

            let list: Vec<String> = channels
                .iter()
                .filter(|x| x.1 == id)
                .map(|x| x.0.clone())
                .collect();

            col.update_one(
                doc! {
                    "_id": id
                },
                doc! {
                    "$set": {
                        "channels": list
                    }
                },
                None,
            )
            .await
            .expect("Failed to update guild.");
        }
    }

    // Reminder to update LATEST_REVISION when adding new migrations.
    LATEST_REVISION
}