use std::path::PathBuf;
use gix::bstr::ByteSlice;
use gix::remote::fetch::Shallow;
use std::path::Path;
use std::sync::atomic::AtomicBool;
use gix::clone::PrepareFetch;
use gix::{create, open, ThreadSafeRepository};
use crate::Package;
use crate::GUIX_REPO_URL;
pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub struct Index {
    repo: ThreadSafeRepository,
    path: PathBuf,
}
impl Index {
    #[inline]
    pub fn new(local_temp_dir: impl AsRef<Path>) -> Result<Self, Error> {
        let path = local_temp_dir.as_ref().join("git.savannah.gnu.org-git-guix.git");
        if !path.exists() {
            let stop = AtomicBool::new(false);
            Self::do_fetch(&path, &stop)?;
        }
        let repo = ThreadSafeRepository::open_opts(&path, open::Options::isolated())?;
        Ok(Self {
            repo, path,
        })
    }
    #[inline(never)]
    #[cold]
    fn do_fetch(path: &Path, stop: &AtomicBool) -> Result<(), Error> {
        let mut pre_fetch = PrepareFetch::new(GUIX_REPO_URL, path, create::Kind::Bare, create::Options::default(), open::Options::isolated())?
            .with_shallow(Shallow::DepthAtRemote(1.try_into().unwrap()));
        pre_fetch.fetch_only(gix::progress::Discard, stop)?;
        Ok(())
    }
    #[inline]
    pub fn update(&self, stop_if_true: Option<&AtomicBool>) -> Result<(), Error> {
        Self::do_fetch(&self.path, stop_if_true.unwrap_or(&AtomicBool::new(false)))
    }
    #[inline(never)]
    pub fn list_all(&self) -> Result<Vec<(String, Vec<Package<String>>)>, Error> {
        let repo = self.repo.to_thread_local();
        let mut tmp = vec![];
        let head = repo.head_commit()?;
        let root = head.tree()?;
        let packages_dir = root.lookup_entry_by_path("gnu/packages", &mut tmp)?.ok_or("missing gnu/packages")?.object()?.peel_to_tree()?;
        packages_dir.iter().filter_map(|entry| {
            let entry = match entry {
                Ok(entry) => entry,
                Err(e) => return Some(Err(Error::from(e))),
            };
            let name = entry.filename().to_str().ok()?.strip_suffix(".scm")?.to_string();
            Some(Ok((name, entry)))
        }).map(|res| {
            let (name, entry) = res?;
            let blob = entry.object()?.peel_to_kind(gix::objs::Kind::Blob)?;
            let scm = String::from_utf8_lossy(&blob.data);
            if scm.trim_start().is_empty() {
                return Ok((name, vec![]));
            }
            let packages = crate::parse_scm(&scm)?.map(|p| Package {
                name: p.name.to_owned(),
                version: p.version.to_owned(),
            }).collect();
            Ok((name, packages))
        })
        .filter(|r| r.as_ref().map_or(true, |(_, pkgs)| !pkgs.is_empty()))
        .collect()
    }
}