Documentation
use cookie::{Cookie, Key, PrivateJar};
use http::HeaderMap;
use mincat_core::{
    error::Error,
    request::{FromRequestParts, Parts},
    response::{IntoResponse, IntoResponseParts, Response},
};

use super::{cookies_from_request, set_cookies, CookieKey};

pub struct PrivateCookieJar {
    jar: cookie::CookieJar,
    key: Key,
}

#[async_trait::async_trait]
impl FromRequestParts for PrivateCookieJar {
    type Error = Error;

    async fn from_request_parts(parts: &mut Parts) -> Result<Self, Self::Error> {
        let CookieKey(key) = parts
            .extensions
            .get::<CookieKey>()
            .ok_or(Error::new("missing state Cookiekey"))?;

        Ok(PrivateCookieJar::from_headers(&parts.headers, key))
    }
}

impl PrivateCookieJar {
    fn from_headers(headers: &HeaderMap, key: &Key) -> Self {
        let mut jar = cookie::CookieJar::new();
        let mut private_jar = jar.private_mut(key);
        for cookie in cookies_from_request(headers) {
            if let Some(cookie) = private_jar.decrypt(cookie) {
                private_jar.add_original(cookie);
            }
        }

        Self {
            jar,
            key: key.clone(),
        }
    }

    pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
        self.private_jar().get(name)
    }

    #[must_use]
    pub fn remove<C: Into<Cookie<'static>>>(mut self, cookie: C) -> Self {
        self.private_jar_mut().remove(cookie);
        self
    }

    #[must_use]
    #[allow(clippy::should_implement_trait)]
    pub fn add<C: Into<Cookie<'static>>>(mut self, cookie: C) -> Self {
        self.private_jar_mut().add(cookie);
        self
    }

    fn private_jar(&self) -> PrivateJar<&'_ cookie::CookieJar> {
        self.jar.private(&self.key)
    }

    fn private_jar_mut(&mut self) -> PrivateJar<&'_ mut cookie::CookieJar> {
        self.jar.private_mut(&self.key)
    }
}

impl IntoResponse for PrivateCookieJar {
    fn into_response(self) -> Response {
        let mut res = ().into_response();
        set_cookies(self.jar, res.headers_mut());
        res
    }
}

impl IntoResponseParts for PrivateCookieJar {
    fn into_response_parts(self, mut response: Response) -> Response {
        set_cookies(self.jar, response.headers_mut());
        response
    }
}