Documentation
use std::{
  fs,
  fs::File,
  path::{Path, PathBuf},
};

use crossfire::AsyncRx;
use current_platform::CURRENT_PLATFORM as TARGET;
use defer_lite::defer;
use down::down;
use pbar::pbar;
use self_replace::self_replace;
use sver::Ver;
use ver_from_txt::VerUrlLi;

use crate::Result;

pub struct Uper {
  pub recv: AsyncRx<u64>,
  pub path: PathBuf,
  pub project: String,
  pub channel: String,
  pub ver: Ver,
}

fn tar_zstd_to_dir<P1: AsRef<Path>, P2: AsRef<Path>>(path: P1, to_dir: P2) -> std::io::Result<()> {
  let file = File::open(path)?;
  let decoder = zstd::stream::read::Decoder::new(file)?;
  let mut archive = tar::Archive::new(decoder);
  archive.unpack(to_dir)?;
  Ok(())
}

impl Uper {
  pub async fn load(
    project: impl Into<String>,
    channel: impl Into<String>,
    ver_url_li: VerUrlLi,
  ) -> Result<Self> {
    let project = project.into();
    let channel = channel.into();
    let tmpdir = std::env::temp_dir().join(format!("uper/{project}/{}", ver_url_li.ver));
    std::fs::create_dir_all(&tmpdir)?;

    let path = tmpdir.join(TARGET);

    if path.exists() {
      if path.is_dir() {
        fs::remove_dir_all(&path)?;
      } else {
        fs::remove_file(&path)?;
      }
    }

    let recv = down(
      ver_url_li
        .url_li
        .into_iter()
        .map(|i| format!("{i}/{TARGET}.tar")),
      &path,
    )
    .await?;
    Ok(Self {
      recv,
      path,
      channel,
      ver: ver_url_li.ver,
      project,
    })
  }

  pub async fn join(self, pk: [u8; 32]) -> Result<()> {
    let recv = self.recv;
    let path = &self.path;

    if let Ok(size) = xerr::ok!(recv.recv().await) {
      let mut pbar = pbar(size);
      pbar.set_message(format!("upgrade {}{}", self.project, self.ver));
      let mut downed = 0;
      while let Ok(d) = recv.recv().await {
        downed = d;
        pbar.set_position(downed)
      }
      let ver = self.ver;
      if downed == size
        && let Some(verfiy_dir) = upgrade_verify::check(vb::e_li(ver.0), path, pk)?
      {
        xerr::log!(std::fs::remove_file(path));
        let tar_zst = verfiy_dir.join("tar.zst");
        xerr::log!(tar_zstd_to_dir(&tar_zst, path));
        xerr::log!(std::fs::remove_dir_all(verfiy_dir));

        pbar.finish_and_clear();

        let exe = path.join(format!("{}{}", &self.project, std::env::consts::EXE_SUFFIX));

        match std::process::Command::new(&exe).args(["-v"]).output() {
          Ok(r) => {
            let stdout = String::from_utf8_lossy(&r.stdout);
            let stdout = stdout.trim();
            if r.status.success() {
              defer! {
                xerr::log!(std::fs::remove_dir_all(path));
              }

              self_replace(&exe)?;
              let down_ver: Ver = stdout.into();
              if down_ver == ver {
                println!("{}{} ( {} channel)", self.project, ver, self.channel,);
              } else {
                eprintln!("❌ down version {down_ver} != {ver}",);
              }
            } else {
              println!("{stdout}");
              eprintln!("{}", String::from_utf8_lossy(&r.stderr));
            }
          }
          Err(err) => {
            eprintln!("{err}");
          }
        };
      }
    }
    Ok(())
  }
}