Skip to content
use actix_web::HttpRequest;
use std::collections::HashSet;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};

pub const SESSION_COOKIE_NAME: &str = "pokedex_session";
static COUNTER: AtomicU64 = AtomicU64::new(1);

#[derive(Clone, Default)]
pub struct SessionStore {
    sessions: Arc<RwLock<HashSet<String>>>,
}

impl SessionStore {
    #[must_use]
    pub fn new() -> Self {
        Self {
            sessions: Arc::new(RwLock::new(HashSet::new())),
        }
    }

    pub fn create_session(&self) -> String {
        let nanos = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_nanos();
        let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
        let token = format!("{nanos:016x}{seq:016x}");
        if let Ok(mut lock) = self.sessions.write() {
            lock.insert(token.clone());
        }
        token
    }

    #[must_use]
    pub fn is_authenticated(&self, req: &HttpRequest) -> bool {
        if let Some(cookie) = req.cookie(SESSION_COOKIE_NAME) {
            let val = cookie.value();
            if let Ok(lock) = self.sessions.read() {
                return lock.contains(val);
            }
        }
        false
    }

    pub fn destroy_session(&self, req: &HttpRequest) {
        if let Some(cookie) = req.cookie(SESSION_COOKIE_NAME) {
            let val = cookie.value();
            if let Ok(mut lock) = self.sessions.write() {
                lock.remove(val);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::cookie::Cookie;
    use actix_web::test::TestRequest;

    #[test]
    fn test_session_lifecycle() {
        let store = SessionStore::new();
        let token = store.create_session();

        let req_valid = TestRequest::default()
            .cookie(Cookie::build(SESSION_COOKIE_NAME, token.clone()).finish())
            .to_http_request();
        assert!(store.is_authenticated(&req_valid));

        let req_invalid = TestRequest::default()
            .cookie(Cookie::build(SESSION_COOKIE_NAME, "wrong_token").finish())
            .to_http_request();
        assert!(!store.is_authenticated(&req_invalid));

        store.destroy_session(&req_valid);
        assert!(!store.is_authenticated(&req_valid));
    }
}