//! Dirigent Auth
//!
//! Tiny user identity crate for the Dirigent system. No async deps.
//! Usable from both server and WASM targets.
//!
//! Provides the core `UserId`, `User`, and `UserProfile` types used
//! throughout the Dirigent ecosystem for ownership tracking and
//! authorization.

pub mod secret;
pub use secret::{SecretSource, SecretError};

pub mod account;
pub use account::{Account, AccountKind, AccountProfile};

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

/// Unique identifier for a user.
///
/// Uses UUID v7 (time-ordered) for new users. Existing string-based
/// IDs should be migrated to UUIDs at creation time.
pub type UserId = Uuid;

/// User information.
///
/// Represents a user in the Dirigent system with profile data and
/// creation timestamp.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct User {
    /// Unique user identifier (UUID v7)
    pub id: UserId,
    /// User profile with optional display fields
    pub profile: UserProfile,
    /// When this user was created
    pub created_at: DateTime<Utc>,
}

impl User {
    /// Create a new user with a generated UUID v7 and the given profile.
    pub fn new(profile: UserProfile) -> Self {
        Self {
            id: Uuid::now_v7(),
            profile,
            created_at: Utc::now(),
        }
    }

    /// Create a new user with a specific ID.
    pub fn with_id(id: UserId, profile: UserProfile) -> Self {
        Self {
            id,
            profile,
            created_at: Utc::now(),
        }
    }

    /// Get the display name, falling back to username or "Unknown".
    pub fn display_name(&self) -> &str {
        self.profile
            .name
            .as_deref()
            .or(self.profile.username.as_deref())
            .unwrap_or("Unknown")
    }
}

/// User profile information.
///
/// All fields are optional to allow progressive enrichment.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct UserProfile {
    /// Display name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// Email address
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    /// Username / handle
    #[serde(skip_serializing_if = "Option::is_none")]
    pub username: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_user_creation() {
        let user = User::new(UserProfile {
            name: Some("Test User".to_string()),
            ..Default::default()
        });
        assert_eq!(user.display_name(), "Test User");
        assert_eq!(user.id.get_version_num(), 7);
    }

    #[test]
    fn test_user_with_id() {
        let id = Uuid::nil();
        let user = User::with_id(
            id,
            UserProfile {
                name: Some("Nil User".to_string()),
                ..Default::default()
            },
        );
        assert_eq!(user.id, Uuid::nil());
        assert_eq!(user.display_name(), "Nil User");
    }

    #[test]
    fn test_display_name_fallbacks() {
        // Falls back to username
        let user = User::new(UserProfile {
            username: Some("jdoe".to_string()),
            ..Default::default()
        });
        assert_eq!(user.display_name(), "jdoe");

        // Falls back to "Unknown"
        let user = User::new(UserProfile::default());
        assert_eq!(user.display_name(), "Unknown");
    }

    #[test]
    fn test_serialization_roundtrip() {
        let user = User::new(UserProfile {
            name: Some("Test".to_string()),
            email: Some("test@example.com".to_string()),
            username: None,
        });

        let json = serde_json::to_string(&user).expect("serialize");
        let deserialized: User = serde_json::from_str(&json).expect("deserialize");

        assert_eq!(deserialized.id, user.id);
        assert_eq!(deserialized.profile.name, user.profile.name);
        assert_eq!(deserialized.profile.email, user.profile.email);
        assert!(deserialized.profile.username.is_none());
    }

    #[test]
    fn test_profile_skip_none_fields() {
        let profile = UserProfile {
            name: Some("Test".to_string()),
            email: None,
            username: None,
        };
        let json = serde_json::to_string(&profile).expect("serialize");
        assert!(!json.contains("email"));
        assert!(!json.contains("username"));
        assert!(json.contains("name"));
    }
}
