use crate::creds::Creds;
use crate::sticker_config::{DiscordDeployLocation, Sticker, StickerConfig, StickerType};
use indexmap::IndexMap;
use serenity::http::client::Http as DiscordClient;
//use serenity::cache::Cache as DiscordCache;

use serenity::model::prelude::Emoji;
use std::collections::{HashMap, VecDeque};

use serde_json::json;

fn split_by(
    pack_contents: &[String],
    sticker_type: StickerType,
    deploy_locations: &[DiscordDeployLocation],
    pack_emojis: &HashMap<String, Sticker>,
) -> IndexMap<String, Vec<(String, Sticker)>> {
    let pack_type_emoji_count = pack_emojis
        .iter()
        .filter(|emoji| emoji.1.r#type == sticker_type)
        .count();

    let max_type_emoji_total: u64 = deploy_locations
        .iter()
        .map(|loc| {
            return match sticker_type {
                StickerType::Regular => loc.max_regular_emoji,
                StickerType::Gif => loc.max_animated_emoji,
                _ => unreachable!("wrong sticker type for discord"),
            };
        })
        .sum();

    if pack_type_emoji_count > max_type_emoji_total.try_into().unwrap() {
        panic!("not enough space in servers for emoji, please add more servers to deploy across")
    }

    let mut pack_type_emojis: VecDeque<(String, Sticker)> = pack_contents
        .iter()
        .filter(|emoji| {
            return pack_emojis.get(*emoji).unwrap().r#type == sticker_type;
        })
        .map(|emoji| {
            return (emoji.clone(), pack_emojis.get(emoji).unwrap().clone());
        })
        .collect();

    let mut emojis_per_server: IndexMap<String, Vec<(String, Sticker)>> = IndexMap::new();
    for deploy_location in deploy_locations.iter() {
        emojis_per_server.insert(deploy_location.deploy_name.clone(), Vec::new());
    }

    'outer: for deploy_location in deploy_locations.iter() {
        let mut sticker_count = 0;

        let max_type_emoji = match sticker_type {
            StickerType::Regular => deploy_location.max_regular_emoji,
            StickerType::Gif => deploy_location.max_animated_emoji,
            _ => unreachable!("wrong sticker type for discord"),
        };

        while sticker_count < max_type_emoji {
            let emoji = pack_type_emojis.pop_front();
            if emoji.is_none() {
                break 'outer;
            }

            emojis_per_server
                .get_mut(&deploy_location.deploy_name)
                .unwrap()
                .push(emoji.unwrap());
            sticker_count += 1;
        }
    }

    emojis_per_server
}

pub async fn deploy_discord(
    deploy_id: String,
    sticker_config: StickerConfig,
    creds: Creds,
    base_stickerdb_path: String,
) {
    let discord_token = creds.discord_bot_token.as_str();
    let discord_client = DiscordClient::new(discord_token);
    //let discord_cache = DiscordCache::new()

    let deploy_where = sticker_config.deploy_where.get(&deploy_id).unwrap();
    let deploy_locations = deploy_where.discord.as_ref().unwrap().clone();
    let deploy_locations_map: HashMap<_, _> = deploy_locations
        .iter()
        .map(|loc| (loc.deploy_name.clone(), loc))
        .collect();

    let pack_contents = sticker_config.sticker_sets.get(&deploy_where.pack_id).unwrap();
    let pack_emojis: HashMap<String, Sticker> = pack_contents
        .iter()
        .map(|emoji_name| {
            return (
                emoji_name.clone(),
                sticker_config.stickers.get(emoji_name).unwrap().clone(),
            );
        })
        .collect();

    let mut emojis_per_server: IndexMap<String, Vec<(String, Sticker)>> = IndexMap::new();
    for deploy_location in deploy_locations.iter() {
        emojis_per_server.insert(deploy_location.deploy_name.clone(), Vec::new());
    }

    // block only so can hide in IDE
    {
        let regular_emoji_per_server = split_by(
            pack_contents,
            StickerType::Regular,
            &deploy_locations,
            &pack_emojis,
        );

        for loc in regular_emoji_per_server.into_iter() {
            emojis_per_server
                .get_mut(&loc.0)
                .unwrap()
                .extend(loc.1.into_iter());
        }

        let gif_emoji_per_server = split_by(
            pack_contents,
            StickerType::Gif,
            &deploy_locations,
            &pack_emojis,
        );

        for loc in gif_emoji_per_server.into_iter() {
            emojis_per_server
                .get_mut(&loc.0)
                .unwrap()
                .extend(loc.1.into_iter());
        }
    }

    for server_k_v in emojis_per_server.iter() {
        let deploy_name = server_k_v.0.to_owned();
        let deploy_emojis = server_k_v.1.to_owned();

        println!("Deploying to {}", deploy_name);

        let deploy_location = deploy_locations_map.get(&deploy_name).unwrap();

        let deploy_emoji_names: Vec<String> = deploy_emojis
            .iter()
            .map(|deploy_emoji| deploy_emoji.0.clone())
            .collect();

        let discord_emojis = discord_client
            .get_emojis(deploy_location.id)
            .await
            .expect("could not fetch discord emoji");
        let discord_emojis_names: Vec<String> = discord_emojis
            .iter()
            .map(|emoji| emoji.name.clone())
            .collect();

        let invalid_emojis: Vec<Emoji> = discord_emojis
            .clone()
            .into_iter()
            .filter(|emoji| !deploy_emoji_names.contains(&emoji.name))
            .collect();

        if !invalid_emojis.is_empty() {
            for emoji in invalid_emojis.iter() {
                println!("Removing Emoji {}", &emoji.name);
                discord_client
                    .delete_emoji(deploy_location.id, emoji.id.0)
                    .await
                    .expect("could not delete emoji")
            }
        }

        let missing_emojis: Vec<String> = deploy_emoji_names
            .clone()
            .into_iter()
            .filter(|emoji| !discord_emojis_names.contains(emoji))
            .collect();

        if !missing_emojis.is_empty() {
            for emoji in missing_emojis.iter() {
                println!("Uploading Emoji {}", &emoji);
                let emoji_data = pack_emojis.get(emoji).unwrap();

                let image_path = std::path::PathBuf::from(&base_stickerdb_path)
                    .join(std::path::PathBuf::from(emoji_data.file.clone()));
                let image_data =
                    serenity::utils::read_image(image_path).expect("could not open emoji file");

                discord_client
                    .create_emoji(
                        deploy_location.id,
                        &json!({
                            "name": emoji,
                            "image": image_data,
                        }),
                        None,
                    )
                    .await
                    .expect("could not upload emoji");
            }
        }

        println!(
            "Missing: {:#?}\nInvalid: {:#?}",
            missing_emojis, invalid_emojis
        );
    }

    //println!("{:#?}", emojis_per_server);

    //println!("{} {} {:?}", max_regular_emoji_total, max_animated_emoji_total, deploy_locations);
}