//! Principal types for IAM
//!
//! Principals represent identities that can be authenticated and authorized.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

/// Kind of principal
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PrincipalKind {
    /// Human user
    User,
    /// Service account (for machine-to-machine auth)
    ServiceAccount,
    /// Group of principals
    Group,
}

impl fmt::Display for PrincipalKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PrincipalKind::User => write!(f, "user"),
            PrincipalKind::ServiceAccount => write!(f, "service_account"),
            PrincipalKind::Group => write!(f, "group"),
        }
    }
}

impl std::str::FromStr for PrincipalKind {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "user" => Ok(PrincipalKind::User),
            "service_account" | "sa" => Ok(PrincipalKind::ServiceAccount),
            "group" => Ok(PrincipalKind::Group),
            _ => Err(format!("unknown principal kind: {}", s)),
        }
    }
}

/// A principal (identity) in the IAM system
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Principal {
    /// Unique identifier
    pub id: String,

    /// Kind of principal
    pub kind: PrincipalKind,

    /// Display name
    pub name: String,

    /// Optional organization ID
    pub org_id: Option<String>,

    /// Optional project ID (for service accounts)
    pub project_id: Option<String>,

    /// Email address (for users)
    pub email: Option<String>,

    /// OIDC subject (for federated users)
    pub oidc_sub: Option<String>,

    /// Node ID (for service accounts bound to nodes)
    pub node_id: Option<String>,

    /// Arbitrary metadata
    pub metadata: HashMap<String, String>,

    /// Creation timestamp (TSO format)
    pub created_at: u64,

    /// Last update timestamp (TSO format)
    pub updated_at: u64,

    /// Whether the principal is enabled
    pub enabled: bool,
}

impl Principal {
    /// Create a new user principal
    pub fn new_user(id: impl Into<String>, name: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            kind: PrincipalKind::User,
            name: name.into(),
            org_id: None,
            project_id: None,
            email: None,
            oidc_sub: None,
            node_id: None,
            metadata: HashMap::new(),
            created_at: 0,
            updated_at: 0,
            enabled: true,
        }
    }

    /// Create a new service account principal
    pub fn new_service_account(
        id: impl Into<String>,
        name: impl Into<String>,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
    ) -> Self {
        Self {
            id: id.into(),
            kind: PrincipalKind::ServiceAccount,
            name: name.into(),
            org_id: Some(org_id.into()),
            project_id: Some(project_id.into()),
            email: None,
            oidc_sub: None,
            node_id: None,
            metadata: HashMap::new(),
            created_at: 0,
            updated_at: 0,
            enabled: true,
        }
    }

    /// Create a new group principal
    pub fn new_group(id: impl Into<String>, name: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            kind: PrincipalKind::Group,
            name: name.into(),
            org_id: None,
            project_id: None,
            email: None,
            oidc_sub: None,
            node_id: None,
            metadata: HashMap::new(),
            created_at: 0,
            updated_at: 0,
            enabled: true,
        }
    }

    /// Get the principal reference string (e.g., "user:alice")
    pub fn to_ref(&self) -> PrincipalRef {
        PrincipalRef {
            kind: self.kind.clone(),
            id: self.id.clone(),
        }
    }
}

/// A reference to a principal (kind:id format)
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PrincipalRef {
    pub kind: PrincipalKind,
    pub id: String,
}

impl PrincipalRef {
    /// Create a new principal reference
    pub fn new(kind: PrincipalKind, id: impl Into<String>) -> Self {
        Self {
            kind,
            id: id.into(),
        }
    }

    /// Create a user reference
    pub fn user(id: impl Into<String>) -> Self {
        Self::new(PrincipalKind::User, id)
    }

    /// Create a service account reference
    pub fn service_account(id: impl Into<String>) -> Self {
        Self::new(PrincipalKind::ServiceAccount, id)
    }

    /// Create a group reference
    pub fn group(id: impl Into<String>) -> Self {
        Self::new(PrincipalKind::Group, id)
    }
}

impl fmt::Display for PrincipalRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}", self.kind, self.id)
    }
}

impl std::str::FromStr for PrincipalRef {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.splitn(2, ':').collect();
        if parts.len() != 2 {
            return Err(format!("invalid principal ref format: {}", s));
        }
        let kind = parts[0].parse()?;
        Ok(PrincipalRef {
            kind,
            id: parts[1].to_string(),
        })
    }
}

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

    #[test]
    fn test_principal_ref_parse() {
        let pr: PrincipalRef = "user:alice".parse().unwrap();
        assert_eq!(pr.kind, PrincipalKind::User);
        assert_eq!(pr.id, "alice");

        let pr: PrincipalRef = "service_account:compute-agent".parse().unwrap();
        assert_eq!(pr.kind, PrincipalKind::ServiceAccount);
        assert_eq!(pr.id, "compute-agent");
    }

    #[test]
    fn test_principal_ref_display() {
        let pr = PrincipalRef::user("alice");
        assert_eq!(pr.to_string(), "user:alice");
    }

    #[test]
    fn test_principal_serialization() {
        let principal = Principal::new_user("u1", "Alice");
        let json = serde_json::to_string(&principal).unwrap();
        let parsed: Principal = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.id, "u1");
        assert_eq!(parsed.name, "Alice");
    }
}
