use std::path::Path;

use crate::db::{Batch, DbOp, Op, Tree, DEFAULT_TREE, SCRIPT_CACHE_KEY};
use crate::prelude::*;

const DEFAULT_CACHE_KEY: &str = "$___DEF_CACHE_KEY_LOC___$";

pub fn get_value(
    db: &mut impl DbOp<String, String>,
    namespace: &str,
    key: &str,
) -> Option<String> {
    db.open_tree(namespace)?;
    db.get_value(key)
}

pub fn list_values(
    db: &mut impl DbOp<String, String>,
    namespace: &str,
) -> Option<Vec<(String, String)>> {
    db.open_tree(namespace)?;
    Some(db.list_all().into_iter().collect())
}

pub fn remove_value(
    db: &mut impl DbOp<String, String>,
    namespace: &str,
    key: &str,
    bypass_check: bool,
) -> Option<String> {
    if !bypass_check {
        check_cache_name(namespace)?;
    }
    let mut consumer = |tree: &mut Tree<String, String>| {
        let value = tree.get_value(key)?;
        let to_delete: Vec<String> = tree
            .iter()
            .filter_map(|(k, v)| if v == &value { Some(k) } else { None })
            .cloned()
            .collect();
        for k in to_delete {
            tree.remove(&*k)?;
        }
        Some(value)
    };

    db.apply_tree(namespace, &mut consumer)
}

pub fn insert_value(
    db: &mut impl DbOp<String, String>,
    namespace: &str,
    aliases: Vec<&str>,
    value: &str,
    bypass_check: bool,
) -> Option<String> {
    if !bypass_check {
        check_cache_name(namespace)?;
    }
    db.open_tree(namespace)?;
    let mut batch = crate::db::Batch::default();
    let keys = db.keys();

    let aliases: Vec<&str> = aliases
        .iter()
        .filter_map(|alias| {
            if keys.contains(&alias.to_string()) {
                None
            } else {
                Some(*alias)
            }
        })
        .collect();

    for hash_alias in &aliases {
        batch.add_insert(hash_alias.to_string(), value.to_string());
    }

    if aliases.is_empty() {
        return None;
    }

    db.apply_batch(batch)?;

    Some(aliases.join(", "))
}

pub fn clear_values(
    db: &mut impl DbOp<String, String>,
    cache_name: &str,
    bypass_check: bool,
) -> Option<()> {
    if !bypass_check {
        check_cache_name(cache_name)?;
    }
    if db.open_tree(cache_name).is_some() {
        db.clear();
        Some(())
    } else {
        None
    }
}

pub fn remove_cache(
    db: &mut impl DbOp<String, String>,
    cache_name: &str,
    bypass_check: bool,
) -> Option<bool> {
    if !bypass_check {
        check_cache_name(cache_name)?;
    }
    Some(db.drop_tree(cache_name))
}

pub fn get_cache_names(db: &mut impl DbOp<String, String>) -> Vec<String> {
    db.tree_names()
        .into_iter()
        .filter(|v| v != DEFAULT_TREE && v != SCRIPT_CACHE_KEY)
        .collect()
}

pub fn merge(
    db: &mut impl DbOp<String, String>,
    key_1: &str,
    key_2: &str,
) -> Option<()> {
    check_cache_name(key_1)?;
    check_cache_name(key_2)?;
    db.merge_trees(key_1, key_2)
}

pub fn set_default_cache(
    db: &mut impl DbOp<String, String>,
    default_cache: &str,
) -> Option<()> {
    check_cache_name(default_cache)?;
    db.open_tree(DEFAULT_TREE)?;
    let _ = db.insert(DEFAULT_CACHE_KEY, default_cache);
    db.open_tree(default_cache)?;
    Some(())
}

pub fn get_default_cache(db: &mut impl DbOp<String, String>) -> Option<String> {
    db.open_tree(DEFAULT_TREE)?;
    db.get_value(DEFAULT_CACHE_KEY)
}

pub fn dump(
    db: &mut impl DbOp<String, String>,
    namespace: Option<&str>,
) -> Option<String> {
    if let Some(ns) = namespace {
        db.apply_tree(ns, &mut move |t| {
            serde_json::to_string_pretty(&CacheJson {
                name: ns.to_string(),
                values: t.list_all(),
            })
            .ok()
        })
    } else {
        let caches: Vec<String> = get_cache_names(db)
            .iter()
            .filter_map(|ns| {
                db.apply_tree(ns, &mut move |t| {
                    serde_json::to_string_pretty(&CacheJson {
                        name: ns.clone(),
                        values: t.list_all(),
                    })
                    .ok()
                })
            })
            .collect();

        Some(format!("[{}]", caches.join(",\n")))
    }
}

pub fn backup(db: &mut impl DbOp<String, String>, path: &Path) -> Option<()> {
    let json = dump(db, None)?;
    std::fs::write(path, json).ok()
}

pub fn flush(db: &impl DbOp<String, String>) -> anyhow::Result<&'static str> {
    db.flush()
}

pub fn restore(db: &mut impl DbOp<String, String>, path: &Path) -> Option<()> {
    let file = File::open(path).ok()?;
    let buf_reader = BufReader::new(file);
    let caches: Vec<CacheJson> = serde_json::from_reader(buf_reader).ok()?;
    for cache in caches {
        let mut batch = Batch::default();
        db.open_tree(&cache.name)?;

        for (key, value) in cache.values {
            batch.add_insert(key, value);
        }
        db.apply_batch(batch)?;
    }
    Some(())
}

fn check_cache_name(cache_name: &str) -> Option<()> {
    if cache_name != DEFAULT_TREE && cache_name != SCRIPT_CACHE_KEY {
        Some(())
    } else {
        println!("{} you cannot do this.", colors::Red.paint("Warning!"));
        None
    }
}

#[derive(Serialize, Deserialize)]
struct CacheJson {
    name: String,
    values: BTreeMap<String, String>,
}
