use chrono::NaiveDate;
use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::error::{Error, Result};

#[derive(Serialize, Deserialize, Clone)]
pub struct Env {
    pub key: String,
    pub value: String,
    pub comment: Option<String>,
    pub expiration_date: Option<NaiveDate>,
}

impl Env {
    pub fn new(
        key: impl Into<String>,
        value: impl Into<String>,
        comment: Option<String>,
        expiration_date: Option<NaiveDate>,
    ) -> Self {
        Self {
            key: key.into(),
            value: value.into(),
            comment,
            expiration_date,
        }
    }

    pub fn from_key_value(key: impl Into<String>, value: impl Into<String>) -> Self {
        Self::new(key, value, None, None)
    }

    pub fn is_expired(&self) -> bool {
        if let Some(date) = self.expiration_date {
            date <= chrono::Local::now().date_naive()
        } else {
            false
        }
    }
}

#[derive(Default, Clone)]
pub struct EnvMap {
    envs: IndexMap<String, Env>,
}

impl Serialize for EnvMap {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let envs: Vec<&Env> = self.envs.values().collect();
        envs.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for EnvMap {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let envs: Vec<Env> = Vec::deserialize(deserializer)?;
        Ok(EnvMap::from(envs))
    }
}

impl EnvMap {
    pub fn as_bytes(&self) -> Result<Vec<u8>> {
        Ok(postcard::to_allocvec(self)?)
    }

    pub fn insert(&mut self, env: Env) {
        self.envs.insert(env.key.clone(), env);
    }

    pub fn insert_from_key_value(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.insert(Env::from_key_value(key, value));
    }

    pub fn remove(&mut self, key: &str) -> Result<()> {
        if self.envs.shift_remove(key).is_none() {
            return Err(Error::EnvDoesNotExist(key.to_string()));
        }
        Ok(())
    }

    pub fn get(&self, key: &str) -> Option<&Env> {
        self.envs.get(key)
    }

    pub fn contains_key(&self, key: &str) -> bool {
        self.envs.contains_key(key)
    }

    pub fn keys(&self) -> impl Iterator<Item = &String> {
        self.envs.keys()
    }

    pub fn len(&self) -> usize {
        self.envs.len()
    }

    pub fn is_empty(&self) -> bool {
        self.envs.is_empty()
    }

    pub fn retain<F>(&mut self, mut f: F)
    where
        F: FnMut(&Env) -> bool,
    {
        self.envs.retain(|_, env| f(env));
    }

    pub fn iter(&self) -> impl Iterator<Item = &Env> {
        self.envs.values()
    }

    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Env> {
        self.envs.values_mut()
    }
}

impl From<IndexMap<String, String>> for EnvMap {
    fn from(map: IndexMap<String, String>) -> Self {
        let mut env_map = Self::default();
        for (k, v) in map {
            env_map.insert(Env::from_key_value(k, v));
        }
        env_map
    }
}

impl From<EnvMap> for IndexMap<String, String> {
    fn from(val: EnvMap) -> Self {
        val.envs.into_iter().map(|(k, v)| (k, v.value)).collect()
    }
}

impl From<Vec<Env>> for EnvMap {
    fn from(envs: Vec<Env>) -> Self {
        let mut env_map = Self::default();
        for env in envs {
            env_map.insert(env);
        }
        env_map
    }
}

impl From<&[u8]> for EnvMap {
    fn from(bytes: &[u8]) -> Self {
        postcard::from_bytes(bytes).expect("Failed to deserialize bytes to EnvMap")
    }
}

impl From<Vec<u8>> for EnvMap {
    fn from(bytes: Vec<u8>) -> Self {
        EnvMap::from(bytes.as_slice())
    }
}

impl IntoIterator for EnvMap {
    type Item = Env;
    type IntoIter = indexmap::map::IntoValues<String, Env>;

    fn into_iter(self) -> Self::IntoIter {
        self.envs.into_values()
    }
}

impl<'a> IntoIterator for &'a EnvMap {
    type Item = &'a Env;
    type IntoIter = indexmap::map::Values<'a, String, Env>;

    fn into_iter(self) -> Self::IntoIter {
        self.envs.values()
    }
}

impl<'a> IntoIterator for &'a mut EnvMap {
    type Item = &'a mut Env;
    type IntoIter = indexmap::map::ValuesMut<'a, String, Env>;

    fn into_iter(self) -> Self::IntoIter {
        self.envs.values_mut()
    }
}
