use crate::models::error::{ModelError, ModelErrorKind};
use crate::models::PluginID;
use chrono::{DateTime, Duration, Utc};
use failure::ResultExt;
use serde_derive::{Deserialize, Serialize};
use serde_json;
use std::fs;
use std::path::PathBuf;

static SYNC_AMOUNT_DEFAULT: u32 = 300;
static CONFIG_NAME: &'static str = "newsflash.json";

#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    backend: Option<String>,
    sync_amount: u32,
    #[serde(with = "json_time")]
    last_sync: DateTime<Utc>,
    #[serde(skip_serializing)]
    #[serde(skip_deserializing)]
    path: PathBuf,
}

impl Config {
    pub fn open(path: &PathBuf) -> Result<Self, ModelError> {
        let path = path.join(CONFIG_NAME);
        if path.as_path().exists() {
            let data = fs::read_to_string(&path).context(ModelErrorKind::IO)?;
            let mut config: Self = serde_json::from_str(&data).context(ModelErrorKind::Json)?;
            config.path = path.clone();
            return Ok(config);
        }

        let config = Config {
            backend: None,
            sync_amount: SYNC_AMOUNT_DEFAULT,
            last_sync: Utc::now() - Duration::weeks(1),
            path: path.clone(),
        };
        config.write()?;
        Ok(config)
    }

    fn write(&self) -> Result<(), ModelError> {
        let data = serde_json::to_string_pretty(self).context(ModelErrorKind::Json)?;
        fs::write(&self.path, data).context(ModelErrorKind::IO)?;
        Ok(())
    }

    pub fn get_backend(&self) -> Option<PluginID> {
        match &self.backend {
            Some(plugin_id) => Some(PluginID::new(plugin_id)),
            None => None,
        }
    }

    pub fn set_backend(&mut self, backend: Option<&PluginID>) -> Result<(), ModelError> {
        self.backend = match backend {
            Some(plugin_id) => Some(plugin_id.to_str().to_owned()),
            None => None,
        };
        self.write()?;
        Ok(())
    }

    pub fn get_sync_amount(&self) -> u32 {
        self.sync_amount
    }

    pub fn set_sync_amount(&mut self, amount: u32) -> Result<(), ModelError> {
        self.sync_amount = amount;
        self.write()?;
        Ok(())
    }

    pub fn get_last_sync(&self) -> DateTime<Utc> {
        self.last_sync
    }

    pub fn set_last_sync(&mut self, time: DateTime<Utc>) -> Result<(), ModelError> {
        self.last_sync = time;
        self.write()?;
        Ok(())
    }
}

mod json_time {
    use chrono::{DateTime, Utc};
    use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

    pub fn time_to_json(t: DateTime<Utc>) -> String {
        t.to_rfc3339()
    }

    pub fn serialize<S: Serializer>(time: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error> {
        time_to_json(*time).serialize(serializer)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<DateTime<Utc>, D::Error> {
        let time: String = Deserialize::deserialize(deserializer)?;
        Ok(DateTime::parse_from_rfc3339(&time).map_err(D::Error::custom)?.with_timezone(&Utc))
    }
}
