use std::{
	collections::HashMap,
	ops::Deref,
	path::{Path, PathBuf},
};

use regex::Regex;

use crate::app::Error;

use super::storage;
use super::Config;

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct MountDir {
	pub source: PathBuf,
	pub name: String,
}

impl TryFrom<storage::MountDir> for MountDir {
	type Error = Error;

	fn try_from(mount_dir: storage::MountDir) -> Result<Self, Self::Error> {
		// TODO validation
		Ok(Self {
			source: sanitize_path(&mount_dir.source),
			name: mount_dir.name,
		})
	}
}

impl From<MountDir> for storage::MountDir {
	fn from(m: MountDir) -> Self {
		Self {
			source: m.source,
			name: m.name,
		}
	}
}

impl Config {
	pub fn set_mounts(&mut self, mount_dirs: Vec<storage::MountDir>) -> Result<(), Error> {
		let mut new_mount_dirs = HashMap::new();
		for mount_dir in mount_dirs {
			let mount_dir = <storage::MountDir as TryInto<MountDir>>::try_into(mount_dir)?;
			new_mount_dirs.insert(mount_dir.name.clone(), mount_dir);
		}
		self.mount_dirs = new_mount_dirs;
		Ok(())
	}

	pub fn resolve_virtual_path<P: AsRef<Path>>(&self, virtual_path: P) -> Result<PathBuf, Error> {
		for (name, mount) in &self.mount_dirs {
			if let Ok(p) = virtual_path.as_ref().strip_prefix(name) {
				return if p.components().count() == 0 {
					Ok(mount.source.clone())
				} else {
					Ok(mount.source.join(p))
				};
			}
		}
		Err(Error::CouldNotMapToRealPath(virtual_path.as_ref().into()))
	}
}

fn sanitize_path(source: &PathBuf) -> PathBuf {
	let path_string = source.to_string_lossy();
	let separator_regex = Regex::new(r"\\|/").unwrap();
	let mut correct_separator = String::new();
	correct_separator.push(std::path::MAIN_SEPARATOR);
	let path_string = separator_regex.replace_all(&path_string, correct_separator.as_str());
	PathBuf::from(path_string.deref())
}

#[cfg(test)]
mod test {
	use super::*;

	#[test]
	fn can_resolve_virtual_paths() {
		let raw_config = storage::Config {
			mount_dirs: vec![storage::MountDir {
				name: "root".to_owned(),
				source: PathBuf::from("test_dir"),
			}],
			..Default::default()
		};

		let config: Config = raw_config.try_into().unwrap();

		let test_cases = vec![
			(vec!["root"], vec!["test_dir"]),
			(
				vec!["root", "somewhere", "something.png"],
				vec!["test_dir", "somewhere", "something.png"],
			),
		];

		for (r#virtual, real) in test_cases {
			let real_path: PathBuf = real.iter().collect();
			let virtual_path: PathBuf = r#virtual.iter().collect();
			let converted_path = config.resolve_virtual_path(&virtual_path).unwrap();
			assert_eq!(converted_path, real_path);
		}
	}

	#[test]
	fn sanitizes_paths() {
		let mut correct_path = PathBuf::new();
		if cfg!(target_os = "windows") {
			correct_path.push("C:\\");
		} else {
			correct_path.push("/usr");
		}
		correct_path.push("some");
		correct_path.push("path");

		let tests = if cfg!(target_os = "windows") {
			vec![
				r#"C:/some/path"#,
				r#"C:\some\path"#,
				r#"C:\some\path\"#,
				r#"C:\some\path\\\\"#,
				r#"C:\some/path//"#,
			]
		} else {
			vec![
				r#"/usr/some/path"#,
				r#"/usr\some\path"#,
				r#"/usr\some\path\"#,
				r#"/usr\some\path\\\\"#,
				r#"/usr\some/path//"#,
			]
		};

		for test in tests {
			let raw_config = storage::Config {
				mount_dirs: vec![storage::MountDir {
					name: "root".to_owned(),
					source: PathBuf::from(test),
				}],
				..Default::default()
			};
			let config: Config = raw_config.try_into().unwrap();
			let converted_path = config.resolve_virtual_path(&PathBuf::from("root")).unwrap();
			assert_eq!(converted_path, correct_path);
		}
	}
}