use {
    crate::Package,
    rayon::prelude::*,
    serde::{Deserialize, Serialize},
    std::{
        collections::HashMap,
        error::Error,
        io::Read,
        sync::{mpsc::Sender, Mutex, PoisonError},
        time::Duration,
    },
    url::Url,
};

#[derive(Clone, Debug, Deserialize, Serialize)]
/// A struct representing all of the packages available in a remote repository
pub struct Repository {
    /// The unique name for this repository
    pub name: String,
    /// The base url for this repository
    pub base_url: Url,
    /// a `HashMap` representing all available packages on this server, with the
    /// keys being the unique name of each package
    pub packages: HashMap<String, Package>,
}

#[derive(Clone, Debug)]
/// Messages sent to the caller thread when fetching an update for a repository
pub enum Message {
    ContentLength(usize),
    BytesRead(usize),
    Failure(String),
    Success(usize),
}

impl Repository {
    pub fn build(name: &str, url: &Url, sender: &Sender<Message>) -> Result<Self, Box<dyn Error>> {
        let url = url.join("packages.ron.zstd")?;
        let resp = ureq::get(url.as_ref())
            .timeout(Duration::from_secs(10))
            .call()?;
        if let Some(val) = resp.header("Content-Length") {
            if let Ok(num) = val.parse::<usize>() {
                sender.send(Message::ContentLength(num))?;
            }
        }
        let reader = resp.into_reader();
        let mut reader = zstd::Decoder::new(reader)?;
        let mut buf = vec![];
        loop {
            let bytes = reader.read(&mut buf)?;
            if bytes == 0 {
                break;
            }
            sender.send(Message::BytesRead(bytes))?;
        }
        let packages = ron::de::from_bytes(&buf)?;
        Ok(Self {
            name: name.to_string(),
            base_url: url,
            packages,
        })
    }

    pub fn search_names(&self, query: &str) -> Result<Vec<&Package>, PoisonError<Vec<&Package>>> {
        let mut results = vec![];
        if let Some(p) = self.packages.get(query) {
            results.push(p);
        }
        let results = Mutex::new(results);
        self.packages.par_iter().for_each(|(k, v)| {
            if k.contains(query) {
                if let Ok(mut r) = results.lock() {
                    r.push(v);
                }
            }
        });
        let results = results.into_inner()?;
        Ok(results)
    }

    pub fn search_all(&self, query: &str) -> Result<Vec<&Package>, PoisonError<Vec<&Package>>> {
        let mut results = vec![];
        if let Some(p) = self.packages.get(query) {
            results.push(p);
        }
        let results = Mutex::new(results);
        self.packages.par_iter().for_each(|(k, v)| {
            if k.contains(query)
                || v.description.contains(query)
                || v.long_description.contains(query)
            {
                if let Ok(mut r) = results.lock() {
                    r.push(v);
                }
            }
        });
        let results = results.into_inner()?;
        Ok(results)
    }
}
