//! Session provider — server-side sessions stored in memory.
//!
//! In production, sessions are stored in Redis or Engram (configured via
//! `session_store = "redis"` or `session_store = "engram"` in `el.toml`).
//! This implementation uses in-memory storage for simplicity and testing.

use crate::{AuthContext, AuthError, AuthProvider, AuthResult, AuthUser, RoleRegistry};
use std::{
    collections::HashMap,
    sync::Mutex,
    time::{Duration, Instant},
};

struct SessionEntry {
    context: AuthContext,
    created_at: Instant,
    ttl: Duration,
}

impl SessionEntry {
    fn is_expired(&self) -> bool {
        self.created_at.elapsed() > self.ttl
    }
}

/// In-memory session store.
pub struct SessionProvider {
    sessions: Mutex<HashMap<String, SessionEntry>>,
    pub ttl: Duration,
}

impl SessionProvider {
    pub fn new() -> Self {
        Self {
            sessions: Mutex::new(HashMap::new()),
            ttl: Duration::from_secs(3600),
        }
    }

    pub fn with_ttl(mut self, seconds: u64) -> Self {
        self.ttl = Duration::from_secs(seconds);
        self
    }

    fn generate_session_id() -> String {
        use std::time::{SystemTime, UNIX_EPOCH};
        let nanos = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.subsec_nanos())
            .unwrap_or(0);
        format!("sess-{:016x}", nanos as u64 ^ 0x7b5e3f1a2c4d6890)
    }

    /// Count active (non-expired) sessions.
    pub fn active_session_count(&self) -> usize {
        let sessions = self.sessions.lock().expect("session lock poisoned");
        sessions.values().filter(|s| !s.is_expired()).count()
    }
}

impl Default for SessionProvider {
    fn default() -> Self {
        Self::new()
    }
}

impl AuthProvider for SessionProvider {
    fn name(&self) -> &'static str {
        "session"
    }

    fn verify(&self, session_id: &str) -> AuthResult<AuthContext> {
        let mut sessions = self.sessions.lock().expect("session lock poisoned");
        // Clean expired sessions
        sessions.retain(|_, v| !v.is_expired());
        sessions
            .get(session_id)
            .filter(|s| !s.is_expired())
            .map(|s| s.context.clone())
            .ok_or(AuthError::SessionNotFound)
    }

    fn issue(&self, user: AuthUser, _role_registry: &RoleRegistry) -> AuthResult<String> {
        let session_id = Self::generate_session_id();
        let ctx = AuthContext::authenticated(user, Vec::new(), &session_id);
        let entry = SessionEntry {
            context: ctx,
            created_at: Instant::now(),
            ttl: self.ttl,
        };
        self.sessions
            .lock()
            .expect("session lock poisoned")
            .insert(session_id.clone(), entry);
        Ok(session_id)
    }

    fn revoke(&self, session_id: &str) -> AuthResult<()> {
        self.sessions
            .lock()
            .expect("session lock poisoned")
            .remove(session_id);
        Ok(())
    }
}
