//! HTTP fetch impl 
use std::path::Path;

use url::Url;
use ureq::Agent;



/// Entry point: fetch a bunch of named paths under a URL into a dir.
///
/// In principle, this should maybe own the agent stuff, but in our
/// current world this only gets called via Server methods, and the
/// Server caches the agent, so...
pub(crate) fn rayon(baseurl: &Url, agent: &Agent, path: &Path,
		paths: Vec<String>) -> FetchResult
{
	let nfiles: u32 = paths.len().try_into().unwrap();
	let pb = crate::util::mk_progress(nfiles.into());


	// Setup rayon pool, and start bouncing over the requests
	use rayon::ThreadPoolBuilder as TPB;
	let njobs = crate::core::jobs::net();
	let pool = TPB::new().num_threads(njobs as usize)
			.build().unwrap();

	let (okfiles, errs) = pool.scope(|_s| {
		use rayon::prelude::*;

		// Run 'em all through
		let fruns = paths.into_par_iter().map(|p| {
			let r = fetch_one(agent, baseurl, path, p);
			pb.inc(1);
			r
		});

		// Collect
		let mut oks = Vec::new();
		let mut errs = Vec::new();
		for fress in fruns.collect_vec_list()
		{
			for fres in fress
			{
				match fres
				{
					Ok(f)  => oks.push(f),
					Err(e) => errs.push(e),
				}
			}
		}

		(oks, errs)
	});


	// And return
	pb.finish();
	let errs = match errs.len() {
		0 => None,
		_ => Some(FetchErrs { errs }),
	};
	let ret = FetchResult { nfiles, okfiles, errs };
	ret
}



/// The final result of a fetching
#[derive(Debug)]
pub(crate) struct FetchResult
{
	/// Attempted files
	pub(crate) nfiles: u32,

	/// Successful files
	pub(crate) okfiles: Vec<String>,

	/// Errors
	pub(crate) errs: Option<FetchErrs>,
}


/// Errors found in a fetching
#[derive(Debug)]
#[derive(thiserror::Error)]
#[error("{errs:?}")]  // kinda fugly...
pub(crate) struct FetchErrs
{
	/// Some number of individual errors
	pub(crate) errs: Vec<GetErr>,
}


/// A fetch error
#[derive(Debug)]
#[derive(thiserror::Error)]
pub(crate) enum GetErr
{
	/// URL building error
	#[error("URL building error: {0}")]
	Url(#[from] url::ParseError),

	/// HTTP error
	#[error("HTTP fetch error: {0}")]
	Http(#[from] Box<ureq::Error>),

	/// Filesystem IO error of some kind
	#[error("File I/O error: {0}")]
	Io(#[from] std::io::Error),
}



/// Do a single fetch
fn fetch_one(agent: &Agent, burl: &Url, dst: &Path, file: String)
		-> Result<String, GetErr>
{
	use std::{fs, io};
	use crate::util::path_join;

	// Unlike get_bytes(), this is writing out to the filesystem, and
	// expects bigger files than "a little text I'll parse in".
	// Still, let's not make it trivial for a broken or malicious
	// server to fill up our disk, and retain _some_ limit to how big
	// a file can be.  In looking at some servers, I see these going
	// up to high double digit megs, so...  let's say a gig is
	// probably generous?
	const LIMIT: u64 = 1 * 1024 * 1024 * 1024;

	// Figure the input URL and output filename
	let inurl = burl.join(&file)?;
	let outpath = path_join(dst, &file);

	// Open up the output file.
	// XXX Maybe imagine tempfiles etc. someday.
	let outfile = fs::File::create(&outpath)?;

	// HTTP responses can come in slow, so may as well wrap this...
	let mut outwrite = io::BufWriter::new(outfile);

	// Make the request
	let resp = match agent.request_url("GET", &inurl).call() {
		Ok(r) => r,
		Err(e) => {
			// Cleanup a bit and bail
			fs::remove_file(&outpath)?;
			return Err(Box::new(e))?;
		},
	};

	// OK, it worked, take our limit and write it in
	use io::Read;
	let mut rdr = resp.into_reader().take(LIMIT);
	let _bytes = io::copy(&mut rdr, &mut outwrite)?;

	// Goodie
	let outfile = outwrite.into_inner().map_err(|e| e.into_error())?;
	outfile.sync_all()?;

	// OK then
	Ok(file)
}
