use std::time::Duration;

use anyhow::{Ok, Result};
use cookie::{
    Cookie as CookieJar,
    time::{Duration as CookieDuration, OffsetDateTime},
    SameSite as CookieSomeSite
};

use crate::utils::Values;


#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SameSite {
    /// The "Strict" `SameSite` attribute.
    Strict,
    /// The "Lax" `SameSite` attribute.
    Lax,
    /// The "None" `SameSite` attribute.
    None
}

#[derive(Debug)]
pub struct Cookie {
    name: String,
    value: String,
    expires: Option<Duration>,
    max_age: Option<Duration>,
    domain: Option<String>,
    path: Option<String>,
    secure: Option<bool>,
    http_only: Option<bool>,
    same_site: Option<SameSite>
}

#[derive(Debug)]
pub struct Cookies {
    pub(crate) cookies: Values,
    pub(crate) new_cookie: Vec<Cookie>,
}

impl Cookie {
    pub fn new(name: &str, value: &str) -> Self {
        return Self {
            name: name.to_string(),
            value: value.to_string(),
            expires: None,
            max_age: None,
            domain: None,
            path: None,
            secure: None,
            http_only: None,
            same_site: None
        }
    }

    pub fn set_name(&mut self, value: &str) -> &mut Self {
        self.name = value.to_string();

        return self;
    }

    pub fn set_value(&mut self, value: &str) -> &mut Self {
        self.value = value.to_string();

        return self;
    }

    pub fn set_expires(&mut self, duration: Duration) -> &mut Self {
        self.expires = Some(duration);

        return self;
    }

    pub fn set_max_age(&mut self, duration: Duration) -> &mut Self {
        self.max_age = Some(duration);

        return self;
    }

    pub fn set_path(&mut self, value: &str) -> &mut Self {
        self.path = Some(value.to_string());

        return self;
    }

    pub fn set_domain(&mut self, value: &str) -> &mut Self {
        self.domain = Some(value.to_string());

        return self;
    }

    pub fn set_secure(&mut self, value: bool) -> &mut Self {
        self.secure = Some(value);

        return self;
    }

    pub fn set_http_only(&mut self, value: bool) -> &mut Self {
        self.http_only = Some(value);

        return self;
    }

    pub fn set_same_site(&mut self, value: SameSite) -> &mut Self {
        self.same_site = Some(value);

        return self;
    }

    pub(crate) fn parse(&mut self) -> String {
        let mut cookie = CookieJar::new(self.name.to_string(), self.value.to_string());

        if let Some(expires) = self.expires {
            cookie.set_expires(OffsetDateTime::now_utc() + CookieDuration::seconds(expires.as_secs() as i64));
        }

        if let Some(max_age) = self.max_age {
            cookie.set_max_age(CookieDuration::new(max_age.as_secs() as i64, 0));
        }

        if let Some(path) = &self.path {
            cookie.set_path(path);
        }

        if let Some(domain) = &self.domain {
            cookie.set_domain(domain);
        }

        if let Some(secure) = self.secure {
            cookie.set_secure(secure);
        }

        if let Some(http_only) = self.http_only {
            cookie.set_http_only(http_only);
        }

        if let Some(same_site) = self.same_site {
            match same_site {
                SameSite::Strict => cookie.set_same_site(CookieSomeSite::Strict),
                SameSite::Lax => cookie.set_same_site(CookieSomeSite::Lax),
                SameSite::None => cookie.set_same_site(CookieSomeSite::None),
            }
        }

        return cookie.to_string();
    }
}

impl Cookies {
    pub fn new(cookies: Values) -> Self {
        return Self {
            cookies: cookies,
            new_cookie: vec![],
        }
    }

    pub fn get(&mut self, name: &str) -> String {
        return self.cookies.get(name).map(|v| String::from(v)).unwrap_or(String::new());
    }

    pub fn set(&mut self, name: &str, value: &str) -> &mut Cookie {
        let idx = self.new_cookie.len();

        self.new_cookie.push(Cookie::new(name, value));

        return &mut self.new_cookie[idx];
    }

    pub fn remove(&mut self, name: &str) -> Result<()> {
        self.cookies.remove(name);

        return Ok(())
    }
}
