//! Identity graph nodes — User, Role, Scope, OAuthToken, Session as
//! strongly-typed Engram node structs.
//!
//! Each node maps to an Engram graph node. The identity graph looks like:
//!
//! ```text
//! User ──has_role──▶ Role ──grants──▶ Scope
//!  │
//!  └──has_session──▶ Session ──authenticated_via──▶ OAuthToken
//! ```

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

// ── User ──────────────────────────────────────────────────────────────────────

/// A user identity node in the Engram graph.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct User {
    /// Stable UUID for this user.
    pub id: Uuid,
    /// Email address — unique identifier for the user.
    pub email: String,
    /// Display name (may differ from email).
    pub display_name: String,
    /// When this user node was created.
    pub created_at: DateTime<Utc>,
}

impl User {
    /// Create a new user with a fresh UUID.
    pub fn new(email: impl Into<String>, display_name: impl Into<String>) -> Self {
        Self {
            id: Uuid::new_v4(),
            email: email.into(),
            display_name: display_name.into(),
            created_at: Utc::now(),
        }
    }

    /// Deserialize from a serde_json::Value (as stored in Engram).
    pub fn from_value(value: &serde_json::Value) -> Option<Self> {
        serde_json::from_value(value.clone()).ok()
    }

    /// Serialize to serde_json::Value for storage in Engram.
    pub fn to_value(&self) -> serde_json::Value {
        serde_json::to_value(self).expect("User is always serializable")
    }
}

// ── Role ──────────────────────────────────────────────────────────────────────

/// A role node — grants a set of named permissions to connected User nodes.
///
/// Edge: `User ──has_role──▶ Role`
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Role {
    pub id: Uuid,
    pub name: String,
    /// Flat list of permission strings (e.g., `"orders:read"`, `"users:write"`).
    pub permissions: Vec<String>,
}

impl Role {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            id: Uuid::new_v4(),
            name: name.into(),
            permissions: Vec::new(),
        }
    }

    pub fn with_permission(mut self, perm: impl Into<String>) -> Self {
        self.permissions.push(perm.into());
        self
    }

    pub fn with_permissions(mut self, perms: impl IntoIterator<Item = impl Into<String>>) -> Self {
        self.permissions.extend(perms.into_iter().map(|p| p.into()));
        self
    }

    pub fn has_permission(&self, perm: &str) -> bool {
        self.permissions.iter().any(|p| p == perm)
    }

    pub fn from_value(value: &serde_json::Value) -> Option<Self> {
        serde_json::from_value(value.clone()).ok()
    }

    pub fn to_value(&self) -> serde_json::Value {
        serde_json::to_value(self).expect("Role is always serializable")
    }
}

// ── Scope ─────────────────────────────────────────────────────────────────────

/// An OAuth scope node.
///
/// Edge: `Role ──grants──▶ Scope`
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Scope {
    pub id: Uuid,
    /// The OAuth scope string (e.g., `"openid"`, `"email"`, `"profile"`).
    pub name: String,
    pub description: String,
}

impl Scope {
    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
        Self {
            id: Uuid::new_v4(),
            name: name.into(),
            description: description.into(),
        }
    }

    pub fn from_value(value: &serde_json::Value) -> Option<Self> {
        serde_json::from_value(value.clone()).ok()
    }

    pub fn to_value(&self) -> serde_json::Value {
        serde_json::to_value(self).expect("Scope is always serializable")
    }
}

// ── OAuthToken ────────────────────────────────────────────────────────────────

/// An OAuth token node — stores hashed tokens so the graph is breach-safe.
///
/// Edge: `Session ──authenticated_via──▶ OAuthToken`
///
/// Tokens are hashed with SHA-256 before storage. The raw token is never
/// persisted — only the hash. Refresh tokens use the same scheme.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OAuthToken {
    pub id: Uuid,
    pub provider: String,
    /// SHA-256 hex hash of the access token.
    pub access_token_hash: String,
    /// SHA-256 hex hash of the refresh token, if present.
    pub refresh_token_hash: Option<String>,
    pub expires_at: DateTime<Utc>,
    /// Scopes granted by this token.
    pub scopes: Vec<String>,
}

impl OAuthToken {
    pub fn new(
        provider: impl Into<String>,
        access_token_hash: impl Into<String>,
        refresh_token_hash: Option<String>,
        expires_at: DateTime<Utc>,
        scopes: Vec<String>,
    ) -> Self {
        Self {
            id: Uuid::new_v4(),
            provider: provider.into(),
            access_token_hash: access_token_hash.into(),
            refresh_token_hash,
            expires_at,
            scopes,
        }
    }

    pub fn is_expired(&self) -> bool {
        Utc::now() >= self.expires_at
    }

    pub fn has_scope(&self, scope: &str) -> bool {
        self.scopes.iter().any(|s| s == scope)
    }

    pub fn from_value(value: &serde_json::Value) -> Option<Self> {
        serde_json::from_value(value.clone()).ok()
    }

    pub fn to_value(&self) -> serde_json::Value {
        serde_json::to_value(self).expect("OAuthToken is always serializable")
    }
}

// ── Session ───────────────────────────────────────────────────────────────────

/// A session node — represents an active authenticated session.
///
/// Edge: `User ──has_session──▶ Session`
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Session {
    pub id: Uuid,
    /// The user this session belongs to.
    pub user_id: Uuid,
    pub created_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
    /// The IP address that created this session.
    pub ip_address: Option<String>,
}

impl Session {
    pub fn new(user_id: Uuid, ttl_seconds: i64, ip_address: Option<String>) -> Self {
        let now = Utc::now();
        let expires_at = now + chrono::Duration::seconds(ttl_seconds);
        Self {
            id: Uuid::new_v4(),
            user_id,
            created_at: now,
            expires_at,
            ip_address,
        }
    }

    pub fn is_expired(&self) -> bool {
        Utc::now() >= self.expires_at
    }

    pub fn from_value(value: &serde_json::Value) -> Option<Self> {
        serde_json::from_value(value.clone()).ok()
    }

    pub fn to_value(&self) -> serde_json::Value {
        serde_json::to_value(self).expect("Session is always serializable")
    }
}

// ── Edge type constants ───────────────────────────────────────────────────────

/// Edge type: User → Role
pub const EDGE_HAS_ROLE: &str = "has_role";
/// Edge type: User → Session
pub const EDGE_HAS_SESSION: &str = "has_session";
/// Edge type: Session → OAuthToken
pub const EDGE_AUTHENTICATED_VIA: &str = "authenticated_via";
/// Edge type: Role → Scope
pub const EDGE_GRANTS: &str = "grants";

// ── Node type constants ───────────────────────────────────────────────────────

pub const NODE_USER: &str = "User";
pub const NODE_ROLE: &str = "Role";
pub const NODE_SCOPE: &str = "Scope";
pub const NODE_OAUTH_TOKEN: &str = "OAuthToken";
pub const NODE_SESSION: &str = "Session";
