use crate::identity::Identity;
use dirs::config_dir;
use nucleo::{Config, Matcher, Utf32Str};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};

#[derive(Serialize, Deserialize)]
pub struct Database {
    pub keys: Vec<Identity>,
}
impl Database {
    pub fn load_or_create(path: &Path) -> anyhow::Result<Self> {
        if !database_exists(path) {
            let d = Database { keys: vec![] };
            write_database(d, path)?;
        }
        load_database(path)
    }
    pub fn write(self, path: &Path) -> anyhow::Result<()> {
        write_database(self, path)
    }
    pub fn fuzzy_search(&self, term: String) -> Vec<Identity> {
        if self.keys.is_empty() {
            return vec![];
        }
        let mut m = Matcher::new(Config::DEFAULT);

        let haystacks = self
            .keys
            .iter()
            .map(|u| {
                (
                    u.clone(),
                    format!(
                        "{:?} {}",
                        u,
                        u.keys.pk().unwrap_or("<pk_unknown>".to_string())
                    ),
                )
            })
            .collect::<Vec<_>>();

        let mut scores = haystacks
            .iter()
            .map(|h| {
                (
                    m.fuzzy_match(
                        Utf32Str::Ascii(h.1.as_bytes()),
                        Utf32Str::Ascii(term.as_bytes()),
                    )
                    .unwrap_or(0),
                    h.0.clone(),
                )
            })
            .collect::<Vec<_>>();

        scores.sort_by_key(|u| u.0);
        scores.reverse();

        let mut best_matches = vec![scores[0].clone()];
        for score in &scores[1..] {
            if score.0 == best_matches[0].0 {
                best_matches.push(score.clone());
            }
        }

        best_matches.iter().map(|u| u.1.clone()).collect()
    }
}

fn database_exists(path: &Path) -> bool {
    path.exists()
}
fn load_database(path: &Path) -> anyhow::Result<Database> {
    let f = std::fs::read_to_string(path)?;
    let data: Database = toml::from_str(&f)?;
    Ok(data)
}
fn write_database(d: Database, path: &Path) -> anyhow::Result<()> {
    let s = toml::to_string(&d)?;
    let s_with_warning = format!(
        "# This file is autogenerated; do not edit! sage v{}\n{}",
        env!("CARGO_PKG_VERSION"),
        s
    );
    std::fs::write(path, s_with_warning)?;
    Ok(())
}

pub fn db_default() -> PathBuf {
    config_dir().unwrap().join("sage.toml")
}
