use std::io::Write;
use std::path::PathBuf;

use indicatif::ProgressBar;
use libpijul::pristine::Base32;
use log::debug;

pub struct Http {
    pub url: String,
    pub channel: String,
    pub client: reqwest::Client,
    pub name: String,
}

impl Http {
    pub async fn download_changes(
        &mut self,
        hashes: &[libpijul::pristine::Hash],
        send: &mut tokio::sync::mpsc::Sender<libpijul::pristine::Hash>,
        path: &mut PathBuf,
        _full: bool,
    ) -> Result<(), anyhow::Error> {
        let progress = ProgressBar::new(hashes.len() as u64);
        progress.set_style(
            indicatif::ProgressStyle::default_bar()
                .template("  Downloading changes {wide_bar} {pos:>5}/{len}"),
        );
        let progress = super::PROGRESS.add(progress);
        for c in hashes {
            debug!("downloading {:?}", c);
            libpijul::changestore::filesystem::push_filename(path, c);
            std::fs::create_dir_all(&path.parent().unwrap())?;
            let path_ = path.with_extension("tmp");
            let mut f = std::fs::File::create(&path_)?;
            libpijul::changestore::filesystem::pop_filename(path);
            let c32 = c.to_base32();
            let url = format!("{}/{}", self.url, super::DOT_DIR);
            let mut res = self
                .client
                .get(&url)
                .query(&[("change", c32)])
                .send()
                .await?;
            debug!("response {:?}", res);
            if !res.status().is_success() {
                return Err((crate::Error::Http {
                    status: res.status(),
                })
                .into());
            }
            while let Some(chunk) = res.chunk().await? {
                debug!("writing {:?}", chunk.len());
                f.write_all(&chunk)?;
            }
            std::fs::rename(&path_, &path_.with_extension("change"))?;
            progress.inc(1);
            if send.send(*c).await.is_err() {
                debug!("err for {:?}", c);
                progress.abandon();
                break;
            }
        }
        if !progress.is_finished() {
            progress.set_style(
                indicatif::ProgressStyle::default_bar()
                    .template("✓ Downloading changes {pos:>5}/{len}"),
            );
            progress.finish();
        }
        Ok(())
    }

    pub async fn upload_changes(
        &self,
        mut local: PathBuf,
        to_channel: Option<&str>,
        changes: &[libpijul::Hash],
    ) -> Result<(), anyhow::Error> {
        let progress = ProgressBar::new(changes.len() as u64);
        progress.set_style(
            indicatif::ProgressStyle::default_bar()
                .template("  Uploading changes   {wide_bar} {pos:>5}/{len}"),
        );
        for c in changes {
            libpijul::changestore::filesystem::push_filename(&mut local, &c);
            let url = self.url.clone() + "/" + super::DOT_DIR;
            let change = std::fs::read(&local)?;
            let mut to_channel = if let Some(ch) = to_channel {
                vec![("to_channel", ch)]
            } else {
                Vec::new()
            };
            let c = c.to_base32();
            to_channel.push(("apply", &c));
            debug!("url {:?} {:?}", url, to_channel);
            self.client
                .post(&url)
                .query(&to_channel)
                .body(change)
                .send()
                .await?;
            progress.inc(1);
            libpijul::changestore::filesystem::pop_filename(&mut local);
        }
        progress.set_style(
            indicatif::ProgressStyle::default_bar().template("✓ Uploading changes {pos:>5}/{len}"),
        );
        progress.finish();
        Ok(())
    }
}
