/*
 ** Copyright (C) 2021 KunoiSayami
 **
 ** This program is free software: you can redistribute it and/or modify
 ** it under the terms of the GNU Affero General Public License as published by
 ** the Free Software Foundation, either version 3 of the License, or
 ** any later version.
 **
 ** This program is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 ** GNU Affero General Public License for more details.
 **
 ** You should have received a copy of the GNU Affero General Public License
 ** along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

use log::{error, warn};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
use std::str::FromStr;
use toml::Value;

#[derive(Deserialize, Serialize, Clone)]
pub struct TomlConfig {
    server: TomlServer,
    telegram: TomlTelegram,
    repository: Option<Vec<TomlRepository>>,
}

impl TryFrom<&str> for TomlConfig {
    type Error = anyhow::Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Ok(toml::from_str(value)?)
    }
}

impl TomlConfig {
    pub fn new<P: AsRef<Path>>(path: P) -> anyhow::Result<TomlConfig> {
        let contents = std::fs::read_to_string(&path);
        if let Err(ref e) = contents {
            error!(
                "Unable read file {}, Error: {:?}",
                path.as_ref().display(),
                e
            );
        };
        let contents = contents?;
        Self::try_from(contents.as_str())
    }

    pub fn server(&self) -> &TomlServer {
        &self.server
    }
    pub fn telegram(&self) -> &TomlTelegram {
        &self.telegram
    }
    pub fn repository(&self) -> &Option<Vec<TomlRepository>> {
        &self.repository
    }
}

#[derive(Deserialize, Serialize, Clone)]
pub struct TomlServer {
    bind: String,
    port: u16,
    secrets: Option<String>,
    token: Option<String>,
}

impl TomlServer {
    pub fn bind(&self) -> &str {
        &self.bind
    }
    pub fn port(&self) -> u16 {
        self.port
    }
    pub fn secrets(&self) -> &Option<String> {
        &self.secrets
    }
    pub fn token(&self) -> &Option<String> {
        &self.token
    }
}

#[derive(Debug, Clone)]
pub struct Telegram {
    bot_token: String,
    api_server: Option<String>,
    send_to: Vec<i64>,
}

impl Telegram {
    pub fn bot_token(&self) -> &str {
        &self.bot_token
    }
    pub fn api_server(&self) -> &Option<String> {
        &self.api_server
    }
    pub fn send_to(&self) -> &Vec<i64> {
        &self.send_to
    }
}

pub fn parse_value(value: &Value) -> Vec<i64> {
    match value {
        Value::String(s) => {
            vec![i64::from_str(s.as_str()).expect("Can't parse string value to i64")]
        }
        Value::Integer(i) => vec![*i],
        Value::Array(v) => v
            .iter()
            .map(|x| match x {
                Value::String(s) => i64::from_str(s).expect("Can't parse array string to i64"),
                Value::Integer(i) => *i,
                _ => panic!("Unexpected value {:?}", x),
            })
            .collect(),
        _ => panic!("Unexpected value {:?}", value),
    }
}

impl From<&TomlTelegram> for Telegram {
    fn from(value: &TomlTelegram) -> Self {
        let receivers: Vec<i64> = parse_value(value.send_to());
        Self {
            bot_token: value.bot_token().clone(),
            api_server: value.api_server().clone(),
            send_to: receivers,
        }
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TomlTelegram {
    bot_token: String,
    api_server: Option<String>,
    send_to: Value,
}

impl TomlTelegram {
    pub fn bot_token(&self) -> &String {
        &self.bot_token
    }
    pub fn api_server(&self) -> &Option<String> {
        &self.api_server
    }
    pub fn send_to(&self) -> &Value {
        &self.send_to
    }
}

#[derive(Debug, Clone)]
pub struct Config {
    server: Server,
    telegram: Telegram,
    repo_mapping: HashMap<String, Repository>,
}

impl Config {
    pub fn server(&self) -> &Server {
        &self.server
    }
    pub fn telegram(&self) -> &Telegram {
        &self.telegram
    }

    #[allow(dead_code)]
    #[deprecated(
        note = "Avoid access from repository directly, use `Self::fetch_repository_configure` function instead."
    )]
    pub fn repo_mapping(&self) -> &HashMap<String, Repository> {
        self.mapping()
    }

    fn mapping(&self) -> &HashMap<String, Repository> {
        &self.repo_mapping
    }

    pub fn new<P: AsRef<Path>>(path: P) -> anyhow::Result<Config> {
        let config = TomlConfig::new(path)?;
        Ok(Self::from(&config))
    }

    pub fn fetch_repository_configure(&self, branch_name: &str) -> Repository {
        let conf = self.mapping().get(branch_name);
        match conf {
            None => RepositoryBuilder::new()
                .set_send_to(self.telegram().send_to().clone())
                .set_secrets(self.server().secrets())
                .set_is_default(true)
                .build(),
            Some(repository) => repository.clone(),
        }
    }

    pub fn build_repository_from_configure(
        default_secret: &String,
        default_target: &Vec<i64>,
        repository: &TomlRepository,
    ) -> Repository {
        let secrets = match repository.secrets() {
            None => default_secret.clone(),
            Some(s) => s.clone(),
        };
        let send_to = match repository.send_to() {
            None => default_target.clone(),
            Some(v) => parse_value(v),
        };
        RepositoryBuilder::new()
            .set_secrets(&secrets)
            .set_branch_ignore(repository.branch_ignore.clone().unwrap_or_default())
            .set_send_to(send_to)
            .set_is_default(false)
            .build()
    }
}

impl From<&TomlConfig> for Config {
    fn from(config: &TomlConfig) -> Self {
        let real_secret = config.server().secrets().clone().unwrap_or_default();
        let real_receiver = parse_value(config.telegram().send_to());
        Self {
            server: Server::from(config.server()),
            telegram: Telegram::from(config.telegram()),
            repo_mapping: {
                let mut m = HashMap::new();
                if let Some(repositories) = config.repository() {
                    for repository in repositories {
                        m.insert(
                            repository.full_name().clone(),
                            Config::build_repository_from_configure(
                                &real_secret,
                                &real_receiver,
                                repository,
                            ),
                        );
                    }
                }
                m
            },
        }
    }
}

#[derive(Debug, Clone)]
pub struct Server {
    bind: String,
    secrets: String,
    token: String,
}

impl From<&TomlServer> for Server {
    fn from(s: &TomlServer) -> Self {
        let warning = "Both secrets and token is blank, please fill last one field to make sure your webhook server safe";
        if s.secrets().is_none() && s.token().is_none() {
            eprintln!("{}", warning);
            warn!("{}", warning);
        } else if let Some(ref secrets) = s.secrets() {
            if secrets.is_empty() {
                if let Some(ref token) = s.token() {
                    if token.is_empty() {
                        eprintln!("{}", warning);
                        warn!("{}", warning)
                    }
                }
            }
        }
        Self {
            bind: format!("{}:{}", s.bind(), s.port()),
            secrets: s.secrets().clone().unwrap_or_else(|| "".to_string()),
            token: s.token().clone().unwrap_or_else(|| "".to_string()),
        }
    }
}

impl Server {
    pub fn bind(&self) -> &String {
        &self.bind
    }
    pub fn secrets(&self) -> &String {
        &self.secrets
    }
    pub fn token(&self) -> &str {
        &self.token
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TomlRepository {
    full_name: String,
    send_to: Option<Value>,
    branch_ignore: Option<Vec<String>>,
    secrets: Option<String>,
}

impl TomlRepository {
    pub fn full_name(&self) -> &String {
        &self.full_name
    }
    pub fn send_to(&self) -> &Option<Value> {
        &self.send_to
    }
    pub fn branch_ignore(&self) -> &Option<Vec<String>> {
        &self.branch_ignore
    }
    pub fn secrets(&self) -> &Option<String> {
        &self.secrets
    }
}

#[derive(Debug, Clone)]
pub struct Repository {
    #[cfg(test)]
    is_default: bool,
    //full_name: String,
    send_to: Vec<i64>,
    branch_ignore: Vec<String>,
    secrets: String,
}

impl Repository {
    /*pub fn full_name(&self) -> &str {
        &self.full_name
    }*/
    pub fn send_to(&self) -> &Vec<i64> {
        &self.send_to
    }
    pub fn branch_ignore(&self) -> &Vec<String> {
        &self.branch_ignore
    }
    pub fn secrets(&self) -> &String {
        &self.secrets
    }
    #[cfg(test)]
    pub fn is_default(&self) -> bool {
        self.is_default
    }
}

impl From<&TomlRepository> for Repository {
    fn from(repo: &TomlRepository) -> Self {
        Self {
            //full_name: repo.full_name().clone(),
            send_to: match repo.send_to() {
                None => vec![],
                Some(v) => parse_value(v),
            },
            branch_ignore: match repo.branch_ignore() {
                Some(v) => v.clone(),
                None => vec![],
            },
            secrets: match repo.secrets() {
                None => "".to_string(),
                Some(ref secret) => secret.clone(),
            },
            #[cfg(test)]
            is_default: true,
        }
    }
}

#[derive(Debug, Default, Clone)]
pub struct RepositoryBuilder {
    send_to: Vec<i64>,
    branch_ignore: Vec<String>,
    secrets: String,
    #[cfg(test)]
    is_default: bool,
}

impl RepositoryBuilder {
    pub fn set_send_to(&mut self, send_to: Vec<i64>) -> &mut Self {
        self.send_to = send_to;
        self
    }
    pub fn set_branch_ignore(&mut self, branch_ignore: Vec<String>) -> &mut Self {
        self.branch_ignore = branch_ignore;
        self
    }
    pub fn set_secrets(&mut self, secrets: &String) -> &mut Self {
        self.secrets = secrets.clone();
        self
    }
    #[cfg(test)]
    pub fn set_is_default(&mut self, default: bool) -> &mut Self {
        self.is_default = default;
        self
    }
    #[cfg(not(test))]
    pub fn set_is_default(&mut self, _: bool) -> &mut Self {
        self
    }
    pub fn build(&self) -> Repository {
        Repository {
            send_to: self.send_to.clone(),
            branch_ignore: self.branch_ignore.clone(),
            secrets: self.secrets.clone(),
            #[cfg(test)]
            is_default: self.is_default,
        }
    }
    pub fn new() -> Self {
        Self {
            #[cfg(test)]
            is_default: true,
            ..Default::default()
        }
    }
}
