//! Shared cookie utilities for building `Set-Cookie` headers.
//!
//! This module centralises the session cookie construction that was previously
//! duplicated across every plugin (`email_password`, `passkey`, `two_factor`,
//! `admin`, `password_management`, `session_management`, `email_verification`).

use crate::config::AuthConfig;
use crate::config::SameSite;

use cookie::{Cookie, SameSite as CookieSameSite};

/// Build a `Set-Cookie` header value for a session token using the `cookie`
/// crate for correct formatting and escaping.
pub fn create_session_cookie(token: &str, config: &AuthConfig) -> String {
    let session_config = &config.session;

    let expires_offset = cookie::time::OffsetDateTime::now_utc()
        + cookie::time::Duration::seconds(session_config.expires_in.num_seconds());

    let same_site = map_same_site(&session_config.cookie_same_site);

    let mut cookie = Cookie::build((&*session_config.cookie_name, token))
        .path("/")
        .expires(expires_offset)
        .secure(session_config.cookie_secure)
        .http_only(session_config.cookie_http_only)
        .same_site(same_site);

    // SameSite=None requires the Secure attribute per the spec
    if matches!(session_config.cookie_same_site, SameSite::None) {
        cookie = cookie.secure(true);
    }

    append_cookie_domain(cookie.build().to_string(), config)
}

/// Build a `Set-Cookie` header value that clears the session cookie.
pub fn create_clear_session_cookie(config: &AuthConfig) -> String {
    let session_config = &config.session;

    let same_site = map_same_site(&session_config.cookie_same_site);

    let mut cookie = Cookie::build((&*session_config.cookie_name, ""))
        .path("/")
        .expires(cookie::time::OffsetDateTime::UNIX_EPOCH)
        .secure(session_config.cookie_secure)
        .http_only(session_config.cookie_http_only)
        .same_site(same_site);

    if matches!(session_config.cookie_same_site, SameSite::None) {
        cookie = cookie.secure(true);
    }

    append_cookie_domain(cookie.build().to_string(), config)
}

fn map_same_site(s: &SameSite) -> CookieSameSite {
    match s {
        SameSite::Strict => CookieSameSite::Strict,
        SameSite::Lax => CookieSameSite::Lax,
        SameSite::None => CookieSameSite::None,
    }
}

fn append_cookie_domain(cookie: String, config: &AuthConfig) -> String {
    if let Some(cookie_domain) = config.effective_session_cookie_domain() {
        format!("{cookie}; Domain={cookie_domain}")
    } else {
        cookie
    }
}

#[cfg(test)]
mod tests {
    use super::{create_clear_session_cookie, create_session_cookie};
    use crate::config::{AuthConfig, SameSite};
    use cookie::{Cookie, SameSite as CookieSameSite};

    #[test]
    fn session_cookie_uses_configured_parent_domain() {
        let config = AuthConfig::new("x".repeat(32)).session_cookie_domain(".athena-auth.com");

        let cookie_str = create_session_cookie("session-token", &config);
        let cookie = Cookie::parse(cookie_str.clone()).expect("session cookie should parse");

        assert_eq!(cookie.name(), "athena-auth.session-token");
        assert_eq!(cookie.value(), "session-token");
        assert!(cookie_str.contains("Domain=.athena-auth.com"));
        assert_eq!(cookie.domain(), Some("athena-auth.com"));
        assert_eq!(cookie.path(), Some("/"));
        assert_eq!(cookie.secure(), Some(true));
        assert_eq!(cookie.http_only(), Some(true));
        assert_eq!(cookie.same_site(), Some(CookieSameSite::Lax));
    }

    #[test]
    fn clear_session_cookie_uses_matching_parent_domain() {
        let config = AuthConfig::new("x".repeat(32)).session_cookie_domain(".athena-auth.com");

        let cookie_str = create_clear_session_cookie(&config);
        let cookie = Cookie::parse(cookie_str.clone()).expect("clear cookie should parse");

        assert!(cookie_str.contains("Domain=.athena-auth.com"));
        assert_eq!(cookie.domain(), Some("athena-auth.com"));
        assert_eq!(cookie.path(), Some("/"));
        assert!(cookie_str.contains("Expires=Thu, 01 Jan 1970"));
    }

    #[test]
    fn same_site_none_forces_secure_even_when_disabled_in_config() {
        let mut config = AuthConfig::new("x".repeat(32));
        config.session.cookie_secure = false;
        config.session.cookie_same_site = SameSite::None;

        let cookie = Cookie::parse(create_session_cookie("session-token", &config))
            .expect("session cookie should parse");

        assert_eq!(cookie.same_site(), Some(CookieSameSite::None));
        assert_eq!(cookie.secure(), Some(true));
    }
}
