pub trait Api {
	fn assemble(&self) -> String;
}

impl<'a> Api for Box<dyn 'a + Api> {
	fn assemble(&self) -> String {
		self.as_ref().assemble()
	}
}

pub enum ApiName {
	GeoIp,
	OpenMeteo,
	OpenStreetMap,
}

pub struct ApiQuery<'a> {
	api: ApiName,
	address: &'a str,
	language: &'a str,
}

impl<'a> ApiQuery<'a> {
	pub fn convert(&self) -> Box<dyn 'a + Api> {
		match &self.api {
			ApiName::GeoIp => Box::new(GeoIpLocationQuery {}),
			ApiName::OpenMeteo => Box::new(OpenMeteoLocationQuery {
				address: self.address,
				language: self.language,
			}),
			ApiName::OpenStreetMap => Box::new(OpenStreetMapLocationQuery {
				address: self.address,
				language: self.language,
			}),
		}
	}

	pub fn geo_ip() -> Self {
		Self {
			api: ApiName::GeoIp,
			..Default::default()
		}
	}

	pub const fn location(api: ApiName, address: &'a str, language: &'a str) -> Self {
		Self { api, address, language }
	}

	pub async fn query<T>(&self) -> reqwest::Result<T>
	where
		T: for<'de> serde::Deserialize<'de>,
	{
		reqwest::get(self.convert().assemble()).await?.json::<T>().await
	}
}

impl<'a> Default for ApiQuery<'a> {
	fn default() -> Self {
		Self {
			api: ApiName::OpenMeteo,
			address: "",
			language: "",
		}
	}
}

pub trait ErrorMessage {
	fn error_message() -> String;
}

pub struct GeoIpLocationQuery;

impl Api for GeoIpLocationQuery {
	fn assemble(&self) -> String {
		String::from("https://api.geoip.rs")
	}
}

pub struct OpenMeteoLocationQuery<'a> {
	address: &'a str,
	language: &'a str,
}

impl<'a> Api for OpenMeteoLocationQuery<'a> {
	fn assemble(&self) -> String {
		format!(
			"https://geocoding-api.open-meteo.com/v1/search?name={}&language={}",
			self.address, self.language
		)
	}
}

pub struct OpenStreetMapLocationQuery<'a> {
	address: &'a str,
	language: &'a str,
}

impl<'a> Api for OpenStreetMapLocationQuery<'a> {
	fn assemble(&self) -> String {
		format!(
			"https://nominatim.openstreetmap.org/search?q={}&accept-language={}&limit=1&format=jsonv2",
			self.address, self.language
		)
	}
}
