use std::{ops::Deref, path::PathBuf, sync::Arc};

use async_trait::async_trait;
use conduwuit::{
	Result, Server,
	config::{Config, check},
	error,
};
use url::Url;

use crate::registration_tokens::{ValidToken, ValidTokenSource};

pub struct Service {
	server: Arc<Server>,
}

const SIGNAL: &str = "SIGUSR1";

#[async_trait]
impl crate::Service for Service {
	fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
		Ok(Arc::new(Self { server: args.server.clone() }))
	}

	async fn worker(self: Arc<Self>) -> Result {
		while self.server.running() {
			if self.server.signal.subscribe().recv().await == Ok(SIGNAL) {
				if let Err(e) = self.handle_reload() {
					error!("Failed to reload config: {e}");
				}
			}
		}

		Ok(())
	}

	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}

impl Deref for Service {
	type Target = Arc<Config>;

	#[inline]
	fn deref(&self) -> &Self::Target { &self.server.config }
}

impl Service {
	/// Get the registration token set in the config file, if it exists.
	#[must_use]
	pub fn get_config_file_token(&self) -> Option<ValidToken> {
		self.registration_token
			.clone()
			.map(|token| ValidToken { token, source: ValidTokenSource::Config })
	}

	/// Get the base domain to use for user-facing URLs.
	#[must_use]
	pub fn get_client_domain(&self) -> Url {
		self.well_known.client.clone().unwrap_or_else(|| {
			let host = self.server_name.host();
			format!("https://{host}")
				.as_str()
				.try_into()
				.expect("server name should be a valid host")
		})
	}

	fn handle_reload(&self) -> Result {
		if self.server.config.config_reload_signal {
			#[cfg(all(feature = "systemd", target_os = "linux"))]
			sd_notify::notify(&[
				sd_notify::NotifyState::Reloading,
				sd_notify::NotifyState::monotonic_usec_now()
					.expect("Failed to read monotonic time"),
			])
			.expect("failed to notify systemd of reloading state");

			let config_paths = self.server.config.config_paths.clone().unwrap_or_default();
			self.reload(&config_paths)?;

			#[cfg(all(feature = "systemd", target_os = "linux"))]
			sd_notify::notify(&[sd_notify::NotifyState::Ready])
				.expect("failed to notify systemd of ready state");
		}

		Ok(())
	}

	pub fn reload(&self, paths: &[PathBuf]) -> Result<Arc<Config>> {
		let old = self.server.config.clone();
		let new = Config::load(paths).and_then(|raw| Config::new(&raw))?;

		check::reload(&old, &new)?;
		self.server.config.update(new)
	}
}
