//! Session module — thin facade over `ontoref_auth` preserving the daemon's
//! historic API surface (`SessionEntry`, `SessionStore`, `SessionView`,
//! `RevokeResult`, `COOKIE_NAME`, `extract_cookie`).
//!
//! Under the hood every type is sourced from `ontoref_auth`. The daemon keeps
//! the multi-project `slug` field on its `SessionEntry` view; `ontoref_auth`
//! models the same concept as `Session::scope`.

use std::time::Duration;

pub use auth::session::SessionView;
use auth::session::{
    self as auth_session, RevokeAuthorisation, RevokeResult as AuthRevokeResult, Session,
    SessionStore as AuthSessionStore,
};

use crate::registry::Role;

/// Cookie name used by the daemon UI for browser sessions.
pub const COOKIE_NAME: &str = "ontoref-session";

/// Default session TTL: 30 days. Mirrors the historic daemon value.
const SESSION_TTL: Duration = Duration::from_secs(30 * 24 * 3600);

/// Daemon-shaped view of an active session.
///
/// Backed by [`auth::Session`]; `slug` corresponds to
/// [`auth::Session::scope`], with the empty string mapped to the
/// daemon-admin scope (`_daemon`) and `None` scopes preserved as empty strings
/// when surfaced to UI templates.
#[derive(Debug, Clone)]
pub struct SessionEntry {
    pub id: String,
    pub slug: String,
    pub role: Role,
    pub key_label: String,
    pub expires: u64,
}

impl SessionEntry {
    fn from_auth(session: &Session) -> Self {
        SessionEntry {
            id: session.id.clone(),
            slug: session.scope.clone().unwrap_or_default(),
            role: session.role.into(),
            key_label: session.key_label.clone(),
            expires: session.expires,
        }
    }
}

/// Outcome of [`SessionStore::revoke_by_id`].
pub enum RevokeResult {
    Revoked,
    NotFound,
    Forbidden,
}

impl From<AuthRevokeResult> for RevokeResult {
    fn from(value: AuthRevokeResult) -> Self {
        match value {
            AuthRevokeResult::Revoked => RevokeResult::Revoked,
            AuthRevokeResult::NotFound => RevokeResult::NotFound,
            AuthRevokeResult::Forbidden => RevokeResult::Forbidden,
        }
    }
}

/// Daemon session store — wraps [`auth::SessionStore`] and projects
/// the multi-project `slug` API the rest of the daemon expects.
pub struct SessionStore {
    inner: AuthSessionStore,
}

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

impl SessionStore {
    pub fn new() -> Self {
        SessionStore {
            inner: AuthSessionStore::new(SESSION_TTL),
        }
    }

    /// Access the underlying `auth::SessionStore` for extractors that
    /// require it (axum [`AuthContext`](auth::AuthContext) wiring).
    pub fn inner(&self) -> &AuthSessionStore {
        &self.inner
    }

    /// Create a new session and return its bearer token (UUID v4).
    pub fn create(&self, slug: String, role: Role, key_label: String) -> String {
        let scope = if slug.is_empty() { None } else { Some(slug) };
        self.inner.create(scope, role.into(), key_label)
    }

    /// Look up a session by bearer token.
    pub fn get(&self, token: &str) -> Option<SessionEntry> {
        self.inner.get(token).as_ref().map(SessionEntry::from_auth)
    }

    /// Revoke a session by its bearer token.
    pub fn revoke(&self, token: &str) {
        self.inner.revoke(token);
    }

    /// Revoke all sessions for a given project slug. Returns the count removed.
    pub fn revoke_all_for_slug(&self, slug: &str) -> usize {
        self.inner.revoke_all_for_scope(Some(slug))
    }

    /// List active sessions scoped to one project slug.
    pub fn list_for_slug(&self, slug: &str) -> Vec<SessionView> {
        self.inner.list_for_scope(Some(slug))
    }

    /// List every active session, across all scopes.
    pub fn list_all(&self) -> Vec<SessionView> {
        self.inner.list_all()
    }

    /// Revoke by public id, enforcing the daemon's project-isolation rules.
    pub fn revoke_by_id(
        &self,
        session_id: &str,
        acting_slug: &str,
        acting_role: &Role,
    ) -> RevokeResult {
        let is_daemon_admin = acting_slug == "_daemon";
        let acting_scope = if is_daemon_admin || acting_slug.is_empty() {
            None
        } else {
            Some(acting_slug)
        };
        let auth = RevokeAuthorisation {
            acting_scope,
            acting_role: (*acting_role).into(),
            is_daemon_admin,
        };
        self.inner.revoke_by_id(session_id, &auth).into()
    }
}

/// Extract the value of a named cookie from a raw `Cookie:` header string.
///
/// Returns an owned `String` to match the daemon's historic signature; the
/// underlying lookup is performed by `auth::extract_cookie`.
pub fn extract_cookie(header: &str, name: &str) -> Option<String> {
    auth_session::extract_cookie(header, name).map(str::to_owned)
}
