use crate::command;
use crate::error::Result;
use regex::{
	Regex,
	RegexBuilder,
};
use serde::{
	Deserialize,
	Serialize,
};
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
const CARGO_METADATA_REGEX: &str =
	r"^\[(?:workspace|package)\.metadata\.git\-cliff\.";
const PYPROJECT_METADATA_REGEX: &str = r"^\[(?:tool)\.git\-cliff\.";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
	#[serde(default)]
	pub changelog: ChangelogConfig,
	#[serde(default)]
	pub git:       GitConfig,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ChangelogConfig {
	pub header:         Option<String>,
	pub body:           Option<String>,
	pub footer:         Option<String>,
	pub trim:           Option<bool>,
	pub postprocessors: Option<Vec<TextProcessor>>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct GitConfig {
	pub conventional_commits:  Option<bool>,
	pub filter_unconventional: Option<bool>,
	pub split_commits:         Option<bool>,
	pub commit_preprocessors:     Option<Vec<TextProcessor>>,
	pub commit_parsers:           Option<Vec<CommitParser>>,
	pub protect_breaking_commits: Option<bool>,
	pub link_parsers:             Option<Vec<LinkParser>>,
	pub filter_commits:           Option<bool>,
	pub tag_pattern:              Option<String>,
	#[serde(with = "serde_regex", default)]
	pub skip_tags:                Option<Regex>,
	#[serde(with = "serde_regex", default)]
	pub ignore_tags:              Option<Regex>,
	pub topo_order:               Option<bool>,
	pub sort_commits:             Option<String>,
	pub limit_commits:            Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitParser {
	#[serde(with = "serde_regex", default)]
	pub message:       Option<Regex>,
	#[serde(with = "serde_regex", default)]
	pub body:          Option<Regex>,
	pub group:         Option<String>,
	pub default_scope: Option<String>,
	pub scope:         Option<String>,
	pub skip:          Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextProcessor {
	#[serde(with = "serde_regex")]
	pub pattern:         Regex,
	pub replace:         Option<String>,
	pub replace_command: Option<String>,
}
impl TextProcessor {
	pub fn replace(
		&self,
		rendered: &mut String,
		command_envs: Vec<(&str, &str)>,
	) -> Result<()> {
		if let Some(text) = &self.replace {
			*rendered = self.pattern.replace_all(rendered, text).to_string();
		} else if let Some(command) = &self.replace_command {
			if self.pattern.is_match(rendered) {
				*rendered =
					command::run(command, Some(rendered.to_string()), command_envs)?;
			}
		}
		Ok(())
	}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinkParser {
	#[serde(with = "serde_regex")]
	pub pattern: Regex,
	pub href:    String,
	pub text:    Option<String>,
}
impl Config {
	pub fn parse(path: &Path) -> Result<Config> {
		let config_builder = if path.file_name() == Some(OsStr::new("Cargo.toml")) ||
			path.file_name() == Some(OsStr::new("pyproject.toml"))
		{
			let contents = fs::read_to_string(path)?;
			let metadata_regex = RegexBuilder::new(
				if path.file_name() == Some(OsStr::new("Cargo.toml")) {
					CARGO_METADATA_REGEX
				} else {
					PYPROJECT_METADATA_REGEX
				},
			)
			.multi_line(true)
			.build()?;
			let contents = metadata_regex.replace_all(&contents, "[");
			config::Config::builder().add_source(config::File::from_str(
				&contents,
				config::FileFormat::Toml,
			))
		} else {
			config::Config::builder().add_source(config::File::from(path))
		};
		Ok(config_builder
			.add_source(
				config::Environment::with_prefix("GIT_CLIFF").separator("__"),
			)
			.build()?
			.try_deserialize()?)
	}
}
#[cfg(test)]
mod test {
	use super::*;
	use pretty_assertions::assert_eq;
	use std::env;
	use std::path::PathBuf;
	#[test]
	fn parse_config() -> Result<()> {
		let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
			.parent()
			.expect("parent directory not found")
			.to_path_buf()
			.join("config")
			.join(crate::DEFAULT_CONFIG);
		const FOOTER_VALUE: &str = "test";
		const TAG_PATTERN_VALUE: &str = "*[0-9]*";
		const IGNORE_TAGS_VALUE: &str = "v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+";
		env::set_var("GIT_CLIFF__CHANGELOG__FOOTER", FOOTER_VALUE);
		env::set_var("GIT_CLIFF__GIT__TAG_PATTERN", TAG_PATTERN_VALUE);
		env::set_var("GIT_CLIFF__GIT__IGNORE_TAGS", IGNORE_TAGS_VALUE);
		let config = Config::parse(&path)?;
		assert_eq!(Some(String::from(FOOTER_VALUE)), config.changelog.footer);
		assert_eq!(
			Some(String::from(TAG_PATTERN_VALUE)),
			config.git.tag_pattern
		);
		assert_eq!(
			Some(String::from(IGNORE_TAGS_VALUE)),
			config
				.git
				.ignore_tags
				.map(|ignore_tags| ignore_tags.to_string())
		);
		Ok(())
	}
}