use crate::containers::VecMap;
use std::collections::HashMap;
use url::Url;
use urlpattern::{UrlPattern, UrlPatternInit, UrlPatternMatchInput};

pub struct PathParams(HashMap<String, Option<String>>);
impl PathParams {
	pub fn get(&self, name: &str) -> &str {
		let s = match self.0.get(name) {
			None => return "",
			Some(s) => s,
		};

		s.as_ref().map(|s| s.as_str()).unwrap_or("")
	}
}

struct UrlPatternWithInput {
	pathname: String,
	pattern: UrlPattern,
}
impl PartialEq for UrlPatternWithInput {
	fn eq(&self, other: &Self) -> bool {
		self.pathname == other.pathname
	}
}
impl Eq for UrlPatternWithInput {
}
pub struct Router<T> {
	patterns: VecMap<UrlPatternWithInput, VecMap<String, T>>,
}

impl<T> Router<T> {
	pub fn new() -> Self {
		Self {
			patterns: VecMap::new(),
		}
	}

	pub fn register(&mut self, method: &str, pathname: &str, value: T) {
		let pathname = pathname.to_string();
		let method = method.to_string();

		let upwi = UrlPatternWithInput {
			pathname: pathname.clone(),
			pattern: UrlPattern::parse(
				UrlPatternInit {
					pathname: Some(pathname),
					..Default::default()
				},
				Default::default(),
			)
			.unwrap(),
		};

		if let Some(methods) = self.patterns.get_mut(&upwi) {
			methods.insert(method, value);
		} else {
			let mut methods = VecMap::new();
			methods.insert(method, value);
			self.patterns.insert(upwi, methods);
		}
	}

	pub fn get(
		&self,
		method: &str,
		url: &str,
	) -> Result<(&T, PathParams), u16> {
		let url = match Url::parse(url) {
			Ok(u) => u,
			Err(_) => match Url::parse(&format!("http://example.com{url}")) {
				Ok(u) => u,
				Err(_) => return Err(400),
			},
		};

		for (upwi, methods) in self.patterns.iter() {
			let pattern = &upwi.pattern;
			let result = pattern.exec(UrlPatternMatchInput::Url(url.clone()));
			let path_groups = match result {
				Ok(Some(res)) => res.pathname.groups,
				_ => continue,
			};

			if let Some(t) = methods.get(method) {
				return Ok((t, PathParams(path_groups)));
			}

			return Err(405);
		}

		Err(404)
	}
}

pub mod methods {
	pub const GET: &'static str = "GET";
	pub const POST: &'static str = "POST";
	pub const PUT: &'static str = "PUT";
}
