Documentation
use anyhow::{Context, Result};
use clap::Parser;
use serde::Deserialize;
use git2::Repository;
use git_url_parse::GitUrl;

pub const DEFAULT_ANNOTATION_LABELS: [&str; 2] = ["TODO", "FIXME"];
pub const DEFAULT_TITLE_FORMAT: &str = "[Postpone] {label}: {line}";
pub const DEFAULT_BODY_FORMAT: &str = r#"
This issue was automatically generated by [Postpone bot](https://github.com/n01e0/ppb).

{label}

{file}:{line_number}

```
{line}
```
"#;
pub const PPB_ISSUE_LABEL: &str = "postpone";

#[derive(Debug, Parser)]
#[clap(author, version, about, long_about=None)]
pub struct Args {
    /// config file
    #[clap(short, long)]
    pub config: Option<String>,
    /// remtoe host
    pub host: Option<String>,
    /// organization
    #[clap(long = "organization")]
    pub organization: Option<String>,
    /// repository
    #[clap(long = "repository")]
    pub repository: Option<String>,
    /// GitHub token
    #[clap(long = "token")]
    pub token: Option<String>,
    /// annotation labels
    /// default: ["TODO", "FIXME"]
    #[clap(long="annotation-labels", value_parser, num_args = 1.., value_delimiter = ',', default_value="TODO,FIXME")]
    pub annotation_labels: Option<Vec<String>>,
    /// title format
    /// you can use following variables
    /// {label} {file} {line_number} {line}
    /// default: "Postpone: {label} {file} {line_number}"
    #[clap(long="title-format", default_value=DEFAULT_TITLE_FORMAT)]
    pub title_format: Option<String>,
    /// body format
    /// you can use following variables
    /// {label} {file} {line_number} {line}
    /// default: "Postpone: {label}\n\n{file}:{line_number}\n\n```\n{line}\n```"
    #[clap(long="body-format", default_value=DEFAULT_BODY_FORMAT)]
    pub body_format: Option<String>,
    /// dry run
    /// will not create issues
    /// default: false
    #[clap(long = "dry-run")]
    pub dry_run: bool,
    /// ignore file
    #[clap(long = "ignore-file", value_parser, num_args = 1.., value_delimiter = ',')]
    pub ignore_file: Option<Vec<String>>,
    /// target directory
    #[clap(long = "target-dir", default_value = ".")]
    pub target_dir: String,
}

#[derive(Debug, Deserialize)]
pub struct ConfigFile {
    host: Option<String>,
    organization: Option<String>,
    repository: Option<String>,
    token: Option<String>,
    pub annotation_labels: Option<Vec<String>>,
    pub title_format: Option<String>,
    pub body_format: Option<String>,
    pub ignore_file: Option<Vec<String>>,
    pub target_dir: Option<String>,
}

#[derive(Debug)]
pub struct Config {
    pub host: String,
    pub organization: String,
    pub repository: String,
    pub token: String,
    pub annotation_labels: Vec<String>,
    pub title_format: String,
    pub body_format: String,
    pub ignore_file: Vec<String>,
    pub target_dir: String,
}

impl Config {
    pub fn new(args: &Args) -> Result<Self> {
        let remote: Option<GitUrl> = if let Ok(repo) = Repository::open(".") {
            if let Ok(origin) = repo.find_remote("origin") {
                origin.url().and_then(|url| GitUrl::parse(url).ok())
            } else {
                None
            }
        } else {
            None
        };

        match args.config {
            Some(ref config_file) => {
                let config_file: ConfigFile = serde_yaml::from_str(&std::fs::read_to_string(config_file)?)?;
                Ok(Config {
                    host: config_file
                        .host
                        .or(args.host.clone())
                        .or(remote.as_ref().map(|url| url.host.clone()).flatten())
                        .unwrap_or(String::from("github.com")),
                    organization: config_file
                        .organization
                        .or(args.organization.clone())
                        .or(remote.as_ref().map(|url| url.owner.clone()).flatten())
                        .with_context(|| "organization must be set")?,
                    repository: config_file
                        .repository
                        .or(args.repository.clone())
                        .or(remote.as_ref().map(|url| url.name.clone()))
                        .with_context(|| "repository must be set")?,
                    token: config_file
                        .token
                        .or(args.token.clone())
                        .or(remote.map(|url| url.token).flatten())
                        .with_context(|| "token must be set")?,
                    annotation_labels: config_file
                        .annotation_labels
                        .or(args.annotation_labels.clone())
                        .unwrap_or_else(|| {
                            DEFAULT_ANNOTATION_LABELS
                                .iter()
                                .map(|s| s.to_string())
                                .collect()
                        }),
                    title_format: config_file
                        .title_format
                        .or(args.title_format.clone())
                        .unwrap_or_else(|| DEFAULT_TITLE_FORMAT.to_string()),
                    body_format: config_file
                        .body_format
                        .or(args.title_format.clone())
                        .unwrap_or_else(|| DEFAULT_BODY_FORMAT.to_string()),
                    ignore_file: config_file
                        .ignore_file
                        .or(args.ignore_file.clone())
                        .unwrap_or(vec![]),
                    target_dir: config_file.target_dir.unwrap_or(args.target_dir.clone()),
                })
            }
            None => Ok(Config {
                host: args
                    .host
                    .clone()
                    .or(remote.as_ref().map(|url| url.host.clone()).flatten())
                    .with_context(|| "remote host must be set")?,
                organization: args
                    .organization
                    .clone()
                    .or(remote.as_ref().map(|url| url.owner.clone()).flatten())
                    .with_context(|| "organization must be set")?,
                repository: args
                    .repository
                    .clone()
                    .or(remote.as_ref().map(|url| url.name.clone()))
                    .with_context(|| "repository must be set")?,
                token: args
                    .token
                    .clone()
                    .or(remote.map(|url| url.token).flatten())
                    .with_context(|| "token must be set")?,
                annotation_labels: args.annotation_labels.clone().unwrap_or_else(|| {
                    DEFAULT_ANNOTATION_LABELS
                        .iter()
                        .map(|s| s.to_string())
                        .collect()
                }),
                title_format: args
                    .title_format
                    .clone()
                    .unwrap_or_else(|| DEFAULT_TITLE_FORMAT.to_string()),
                body_format: args
                    .body_format
                    .clone()
                    .unwrap_or_else(|| DEFAULT_BODY_FORMAT.to_string()),
                ignore_file: args.ignore_file.clone().unwrap_or_else(|| vec![]),
                target_dir: args.target_dir.clone(),
            }),
        }
    }
}