use std::io::prelude::*;
use std::path::Path;

use anyhow::Result;

use crate::environment::Environment;

pub fn extract_zip(message: &str, zip_bytes: &[u8], dir_path: &Path, environment: &impl Environment) -> Result<()> {
  // adapted from https://github.com/mvdnes/zip-rs/blob/master/examples/extract.rs
  let reader = std::io::Cursor::new(&zip_bytes);
  let mut zip = zip::ZipArchive::new(reader)?;
  let length = zip.len();

  log_debug!(environment, "Extracting zip file to directory: {}", dir_path.display());

  environment.log_action_with_progress(
    message,
    move |update_size| -> Result<()> {
      // todo: consider parallelizing this
      for i in 0..zip.len() {
        update_size(i);
        let mut file = zip.by_index(i).unwrap();
        if let Some(file_name) = file.enclosed_name() {
          let file_path = dir_path.join(file_name);

          if !file.is_dir() {
            if let Some(parent_dir_path) = file_path.parent() {
              environment.mk_dir_all(parent_dir_path)?;
            }
            let mut file_bytes = Vec::new();
            file_bytes.try_reserve_exact(file.size() as usize)?;
            file.read_to_end(&mut file_bytes)?;
            environment.write_file_bytes(&file_path, &file_bytes)?;
          } else {
            environment.mk_dir_all(&file_path)?;
          }

          // Get and Set permissions
          #[cfg(unix)]
          if environment.is_real() {
            use std::fs;
            use std::os::unix::fs::PermissionsExt;

            if let Some(mode) = file.unix_mode() {
              #[allow(clippy::disallowed_methods)]
              fs::set_permissions(&file_path, fs::Permissions::from_mode(mode))
                .map_err(|err| anyhow::anyhow!("Error setting permissions to {} for file {}: {:#}", mode, file_path.display(), err))?
            }
          }
        } else {
          log_warn!(environment, "Ignoring path in zip because it was not enclosed: {}", file.name());
        }
      }

      Ok(())
    },
    length,
  )?;

  Ok(())
}
