use serde::{Deserialize, Serialize};
use std::{
	net::{IpAddr, Ipv4Addr, SocketAddr},
	path::Path,
};

const CONFIG_FILE: &str = "config.toml";

#[derive(Deserialize, Serialize)]
pub struct Config {
	#[serde(default = "Config::default_admin_passwords")]
	pub admin_passwords: Vec<String>,
	/// (seconds)
	#[serde(default = "Config::default_antispam_duration")]
	pub antispam_duration: u64,
	#[serde(default = "Config::default_antispam_enable")]
	pub antispam_enable: bool,
	/// Maximum number of mutations by IP within antispam_duration
	#[serde(default = "Config::default_antispam_mutation_limit")]
	pub antispam_mutation_limit: u32,
	#[serde(default = "Config::default_antispam_whitelist")]
	pub antispam_whitelist: Vec<IpAddr>,
	/// New or edited comments need admin's approval before being public
	#[serde(default = "Config::default_comment_approve")]
	pub comment_approve: bool,
	/// Duration for which a comment can be edited
	#[serde(default = "Config::default_comment_edit_timeout")]
	pub comment_edit_timeout: u64,
	#[serde(default = "Config::default_comment_author_max_len")]
	pub comment_author_max_len: usize,
	#[serde(default = "Config::default_comment_email_max_len")]
	pub comment_email_max_len: usize,
	#[serde(default = "Config::default_comment_text_max_len")]
	pub comment_text_max_len: usize,
	#[serde(default = "Config::default_cookies_https_only")]
	pub cookies_https_only: bool,
	#[serde(default = "Config::default_cookies_domain")]
	pub cookies_domain: Option<String>,
	#[serde(default = "Config::default_lang")]
	pub lang: String,
	#[serde(default = "Config::default_listen")]
	pub listen: SocketAddr,
	/// Send a matrix message on new comment
	#[serde(default = "Config::default_matrix_notify")]
	pub matrix_notify: bool,
	#[serde(default = "Config::default_matrix_password")]
	pub matrix_password: String,
	/// If connection fails, retry connecting after minimum this duration (seconds)
	#[serde(default = "Config::default_matrix_retry_timeout")]
	pub matrix_retry_timeout: u64,
	#[serde(default = "Config::default_matrix_room")]
	pub matrix_room: String,
	#[serde(default = "Config::default_matrix_server")]
	pub matrix_server: String,
	#[serde(default = "Config::default_matrix_user")]
	pub matrix_user: String,
	/// Are we behind a reverse proxy?
	/// Determines whether we assume client address is in a Forwarded header or socket address.
	#[serde(default = "Config::default_reverse_proxy")]
	pub reverse_proxy: bool,
	#[serde(default = "Config::default_root_url")]
	pub root_url: String,
	/// Templates directory. May be absolute or relative to config/data directory.
	#[serde(default = "Config::default_templates_dir")]
	pub templates_dir: String,
}

impl Config {
	fn default_admin_passwords() -> Vec<String> {
		vec![]
	}
	fn default_antispam_duration() -> u64 {
		3600
	}
	fn default_antispam_enable() -> bool {
		true
	}
	fn default_antispam_mutation_limit() -> u32 {
		10
	}
	fn default_antispam_whitelist() -> Vec<IpAddr> {
		vec![[127u8, 0, 0, 1].into(), [0u8; 4].into(), [0u8; 16].into()]
	}
	fn default_comment_approve() -> bool {
		true
	}
	fn default_comment_edit_timeout() -> u64 {
		7 * 86400
	}
	fn default_comment_author_max_len() -> usize {
		64
	}
	fn default_comment_email_max_len() -> usize {
		128
	}
	fn default_comment_text_max_len() -> usize {
		128 * 1024
	}
	fn default_cookies_https_only() -> bool {
		false
	}
	fn default_cookies_domain() -> Option<String> {
		None
	}
	fn default_lang() -> String {
		"en_GB".into()
	}
	fn default_listen() -> SocketAddr {
		SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 31720)
	}
	fn default_matrix_notify() -> bool {
		false
	}
	fn default_matrix_password() -> String {
		"".into()
	}
	fn default_matrix_retry_timeout() -> u64 {
		3600
	}
	fn default_matrix_room() -> String {
		"#maintenance:matrix.txmn.tk".into()
	}
	fn default_matrix_server() -> String {
		"https://matrix.txmn.tk".into()
	}
	fn default_matrix_user() -> String {
		"@tuxmain:matrix.txmn.tk".into()
	}
	fn default_reverse_proxy() -> bool {
		false
	}
	fn default_root_url() -> String {
		"/".into()
	}
	fn default_templates_dir() -> String {
		"templates".into()
	}
}

impl Default for Config {
	fn default() -> Self {
		Self {
			admin_passwords: Self::default_admin_passwords(),
			antispam_duration: Self::default_antispam_duration(),
			antispam_enable: Self::default_antispam_enable(),
			antispam_mutation_limit: Self::default_antispam_mutation_limit(),
			antispam_whitelist: Self::default_antispam_whitelist(),
			comment_approve: Self::default_comment_approve(),
			comment_edit_timeout: Self::default_comment_edit_timeout(),
			comment_author_max_len: Self::default_comment_author_max_len(),
			comment_email_max_len: Self::default_comment_email_max_len(),
			comment_text_max_len: Self::default_comment_text_max_len(),
			cookies_https_only: Self::default_cookies_https_only(),
			cookies_domain: Self::default_cookies_domain(),
			lang: Self::default_lang(),
			listen: Self::default_listen(),
			matrix_notify: Self::default_matrix_notify(),
			matrix_password: Self::default_matrix_password(),
			matrix_retry_timeout: Self::default_matrix_retry_timeout(),
			matrix_room: Self::default_matrix_room(),
			matrix_server: Self::default_matrix_server(),
			matrix_user: Self::default_matrix_user(),
			reverse_proxy: Self::default_reverse_proxy(),
			root_url: Self::default_root_url(),
			templates_dir: Self::default_templates_dir(),
		}
	}
}

pub fn read_config(dir: &Path) -> Config {
	let path = dir.join(CONFIG_FILE);

	if !path.is_file() {
		let config = Config::default();
		std::fs::write(path, toml_edit::easy::to_string_pretty(&config).unwrap())
			.expect("Cannot write config file");
		config
	} else {
		toml_edit::easy::from_str(
			std::str::from_utf8(&std::fs::read(path).expect("Cannot read config file"))
				.expect("Bad encoding in config file"),
		)
		.expect("Bad TOML in config file")
	}
}

pub fn write_config(dir: &Path, config: &Config) {
	let path = dir.join(CONFIG_FILE);
	std::fs::write(path, toml_edit::easy::to_string_pretty(&config).unwrap())
		.expect("Cannot write config file");
}