use crate::error::Error;
use crate::fs::FS;
use crate::util;
use crate::view::TransferView;
use reqwest::blocking::Client;
use sha2::{Digest, Sha256, Sha512};
use std::fs::File;
use std::io;
use std::path::Path;
use std::time::Duration;

const REQUEST_TIMEOUT_SEC: u64 = 10;

#[derive(Default)]
pub struct Checksum {
    pub sha512: String,
    pub sha256: String,
}

struct ProgressWriter {
    file: File,
    size: Option<u64>,
    written: u64,
    view: TransferView,
    sha512: Sha512,
    sha256: Sha256,
}

impl ProgressWriter {
    pub fn new(file: File, size: Option<u64>, view: TransferView) -> Self {
        Self {
            file,
            size,
            written: 0,
            view,
            sha512: Sha512::new(),
            sha256: Sha256::new(),
        }
    }
}

impl io::Write for ProgressWriter {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.written += buf.len() as u64;
        self.sha512.update(buf);
        self.sha256.update(buf);
        self.view.update(self.written, self.size);
        self.file.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.file.flush()
    }
}

pub struct WebClient {
    client: Client,
}

impl WebClient {
    pub fn new() -> Result<Self, Error> {
        Ok(WebClient {
            client: reqwest::blocking::Client::builder()
                .timeout(Duration::from_secs(REQUEST_TIMEOUT_SEC))
                .gzip(true)
                .brotli(true)
                .build()
                .map_err(Error::Web)?,
        })
    }

    pub fn get_file_size(&mut self, url: &str) -> Result<Option<u64>, Error> {
        Ok(self
            .client
            .head(url)
            .send()
            .map_err(Error::Web)?
            .headers()
            .get("Content-Length")
            .and_then(|value| value.to_str().ok())
            .and_then(|value| value.parse().ok()))
    }

    pub fn download_file(
        &self,
        url: &str,
        file_path: &str,
        view: TransferView,
    ) -> Result<Checksum, Error> {
        let fs = FS::new();

        let temp_file = format!("{file_path}.tmp");
        if Path::new(&temp_file).exists() {
            fs.remove_file(&temp_file)?;
        }

        if Path::new(&file_path).exists() {
            return Result::Ok(Checksum::default());
        }

        let mut resp = reqwest::blocking::get(url).map_err(Error::Web)?;

        let mut writer =
            ProgressWriter::new(fs.create_file(&temp_file)?, resp.content_length(), view);
        resp.copy_to(&mut writer).map_err(Error::Web)?;

        fs.rename_file(&temp_file, file_path)?;

        Ok(Checksum {
            sha512: util::hex_encode(&writer.sha512.clone().finalize()),
            sha256: util::hex_encode(&writer.sha256.clone().finalize()),
        })
    }

    pub fn download_content(&mut self, url: &str) -> Result<String, Error> {
        self.client
            .get(url)
            .send()
            .map_err(Error::Web)?
            .text()
            .map_err(Error::Web)
    }
}
