//! Audit event types
//!
//! Defines structured audit events for IAM operations.

use std::collections::HashMap;
use std::net::IpAddr;

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

use iam_types::Scope;

/// Audit event
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
    /// Unique event ID
    pub id: String,

    /// Event timestamp
    pub timestamp: DateTime<Utc>,

    /// Event kind
    pub kind: AuditEventKind,

    /// Principal ID (if known)
    pub principal_id: Option<String>,

    /// Source IP address
    pub source_ip: Option<IpAddr>,

    /// Request ID (for correlation)
    pub request_id: Option<String>,

    /// Session ID
    pub session_id: Option<String>,

    /// Additional metadata
    pub metadata: HashMap<String, String>,
}

/// Kind of audit event
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AuditEventKind {
    /// Authentication event
    Authn(AuthnEventData),

    /// Authorization event
    Authz(AuthzEventData),

    /// Policy change event
    Policy(PolicyEventData),

    /// Token event (issue, revoke, refresh)
    Token(TokenEventData),

    /// Administrative action
    Admin(AdminEventData),
}

/// Authentication event data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthnEventData {
    /// Whether authentication succeeded
    pub success: bool,

    /// Authentication method used (jwt, mtls, api_key, etc.)
    pub method: String,

    /// Failure reason (if failed)
    pub failure_reason: Option<String>,

    /// Token issuer (if JWT)
    pub issuer: Option<String>,

    /// Client certificate subject (if mTLS)
    pub cert_subject: Option<String>,
}

/// Authorization event data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthzEventData {
    /// Whether authorization was granted
    pub allowed: bool,

    /// Action being authorized
    pub action: String,

    /// Resource being accessed
    pub resource_kind: String,
    pub resource_id: String,

    /// Scope of the request
    pub scope: Scope,

    /// Matching policy ID (if allowed)
    pub policy_id: Option<String>,

    /// Denial reason (if denied)
    pub denial_reason: Option<String>,

    /// Roles evaluated
    pub roles_evaluated: Vec<String>,
}

/// Policy change event data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyEventData {
    /// Type of change
    pub change_type: PolicyChangeType,

    /// Policy ID
    pub policy_id: String,

    /// Policy name
    pub policy_name: Option<String>,

    /// Scope affected
    pub scope: Scope,

    /// Actor who made the change
    pub actor_id: String,
}

/// Type of policy change
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PolicyChangeType {
    Created,
    Updated,
    Deleted,
    Attached,
    Detached,
}

/// Token event data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenEventData {
    /// Type of token event
    pub event_type: TokenEventType,

    /// Token ID or session ID
    pub token_id: Option<String>,

    /// Token TTL (if issued)
    pub ttl_seconds: Option<u64>,

    /// Scope of the token
    pub scope: Option<Scope>,
}

/// Type of token event
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TokenEventType {
    Issued,
    Refreshed,
    Revoked,
    Expired,
}

/// Administrative action event data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminEventData {
    /// Type of admin action
    pub action: String,

    /// Target entity type
    pub target_type: String,

    /// Target entity ID
    pub target_id: String,

    /// Details of the action
    pub details: Option<String>,
}

impl AuditEvent {
    /// Create a new audit event
    pub fn new(kind: AuditEventKind) -> Self {
        Self {
            id: Uuid::new_v4().to_string(),
            timestamp: Utc::now(),
            kind,
            principal_id: None,
            source_ip: None,
            request_id: None,
            session_id: None,
            metadata: HashMap::new(),
        }
    }

    /// Create an authentication success event
    pub fn authn_success(principal_id: &str, method: &str) -> Self {
        Self::new(AuditEventKind::Authn(AuthnEventData {
            success: true,
            method: method.to_string(),
            failure_reason: None,
            issuer: None,
            cert_subject: None,
        }))
        .with_principal(principal_id)
    }

    /// Create an authentication failure event
    pub fn authn_failure(method: &str, reason: &str) -> Self {
        Self::new(AuditEventKind::Authn(AuthnEventData {
            success: false,
            method: method.to_string(),
            failure_reason: Some(reason.to_string()),
            issuer: None,
            cert_subject: None,
        }))
    }

    /// Create an authorization allowed event
    pub fn authz_allowed(
        principal_id: &str,
        action: &str,
        resource_kind: &str,
        resource_id: &str,
        scope: Scope,
    ) -> Self {
        Self::new(AuditEventKind::Authz(AuthzEventData {
            allowed: true,
            action: action.to_string(),
            resource_kind: resource_kind.to_string(),
            resource_id: resource_id.to_string(),
            scope,
            policy_id: None,
            denial_reason: None,
            roles_evaluated: Vec::new(),
        }))
        .with_principal(principal_id)
    }

    /// Create an authorization denied event
    pub fn authz_denied(
        principal_id: &str,
        action: &str,
        resource_kind: &str,
        resource_id: &str,
        scope: Scope,
        reason: &str,
    ) -> Self {
        Self::new(AuditEventKind::Authz(AuthzEventData {
            allowed: false,
            action: action.to_string(),
            resource_kind: resource_kind.to_string(),
            resource_id: resource_id.to_string(),
            scope,
            policy_id: None,
            denial_reason: Some(reason.to_string()),
            roles_evaluated: Vec::new(),
        }))
        .with_principal(principal_id)
    }

    /// Create a token issued event
    pub fn token_issued(
        principal_id: &str,
        token_id: &str,
        ttl_seconds: u64,
        scope: Scope,
    ) -> Self {
        Self::new(AuditEventKind::Token(TokenEventData {
            event_type: TokenEventType::Issued,
            token_id: Some(token_id.to_string()),
            ttl_seconds: Some(ttl_seconds),
            scope: Some(scope),
        }))
        .with_principal(principal_id)
    }

    /// Create a token revoked event
    pub fn token_revoked(principal_id: &str, token_id: &str) -> Self {
        Self::new(AuditEventKind::Token(TokenEventData {
            event_type: TokenEventType::Revoked,
            token_id: Some(token_id.to_string()),
            ttl_seconds: None,
            scope: None,
        }))
        .with_principal(principal_id)
    }

    /// Create a policy created event
    pub fn policy_created(
        actor_id: &str,
        policy_id: &str,
        policy_name: &str,
        scope: Scope,
    ) -> Self {
        Self::new(AuditEventKind::Policy(PolicyEventData {
            change_type: PolicyChangeType::Created,
            policy_id: policy_id.to_string(),
            policy_name: Some(policy_name.to_string()),
            scope,
            actor_id: actor_id.to_string(),
        }))
        .with_principal(actor_id)
    }

    /// Create a policy deleted event
    pub fn policy_deleted(actor_id: &str, policy_id: &str, scope: Scope) -> Self {
        Self::new(AuditEventKind::Policy(PolicyEventData {
            change_type: PolicyChangeType::Deleted,
            policy_id: policy_id.to_string(),
            policy_name: None,
            scope,
            actor_id: actor_id.to_string(),
        }))
        .with_principal(actor_id)
    }

    /// Set principal ID
    pub fn with_principal(mut self, principal_id: &str) -> Self {
        self.principal_id = Some(principal_id.to_string());
        self
    }

    /// Set source IP
    pub fn with_source_ip(mut self, ip: IpAddr) -> Self {
        self.source_ip = Some(ip);
        self
    }

    /// Set request ID
    pub fn with_request_id(mut self, request_id: &str) -> Self {
        self.request_id = Some(request_id.to_string());
        self
    }

    /// Set session ID
    pub fn with_session_id(mut self, session_id: &str) -> Self {
        self.session_id = Some(session_id.to_string());
        self
    }

    /// Add metadata
    pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
        self.metadata.insert(key.to_string(), value.to_string());
        self
    }

    /// Serialize to JSON
    pub fn to_json(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string(self)
    }

    /// Serialize to pretty JSON
    pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
        serde_json::to_string_pretty(self)
    }
}

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

    #[test]
    fn test_authn_success_event() {
        let event = AuditEvent::authn_success("alice", "jwt")
            .with_source_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)))
            .with_request_id("req-123");

        assert_eq!(event.principal_id, Some("alice".to_string()));
        assert!(matches!(event.kind, AuditEventKind::Authn(ref data) if data.success));

        let json = event.to_json().unwrap();
        assert!(json.contains("alice"));
        assert!(json.contains("jwt"));
    }

    #[test]
    fn test_authn_failure_event() {
        let event = AuditEvent::authn_failure("jwt", "Token expired");

        assert!(event.principal_id.is_none());
        match &event.kind {
            AuditEventKind::Authn(data) => {
                assert!(!data.success);
                assert_eq!(data.failure_reason, Some("Token expired".to_string()));
            }
            _ => panic!("Expected Authn event"),
        }
    }

    #[test]
    fn test_authz_allowed_event() {
        let event = AuditEvent::authz_allowed(
            "alice",
            "read",
            "instance",
            "vm-1",
            Scope::project("proj-1", "org-1"),
        );

        assert_eq!(event.principal_id, Some("alice".to_string()));
        match &event.kind {
            AuditEventKind::Authz(data) => {
                assert!(data.allowed);
                assert_eq!(data.action, "read");
            }
            _ => panic!("Expected Authz event"),
        }
    }

    #[test]
    fn test_authz_denied_event() {
        let event = AuditEvent::authz_denied(
            "bob",
            "delete",
            "instance",
            "vm-1",
            Scope::project("proj-1", "org-1"),
            "No matching policy",
        );

        match &event.kind {
            AuditEventKind::Authz(data) => {
                assert!(!data.allowed);
                assert_eq!(data.denial_reason, Some("No matching policy".to_string()));
            }
            _ => panic!("Expected Authz event"),
        }
    }

    #[test]
    fn test_token_issued_event() {
        let event = AuditEvent::token_issued("alice", "session-123", 3600, Scope::System);

        match &event.kind {
            AuditEventKind::Token(data) => {
                assert!(matches!(data.event_type, TokenEventType::Issued));
                assert_eq!(data.ttl_seconds, Some(3600));
            }
            _ => panic!("Expected Token event"),
        }
    }

    #[test]
    fn test_policy_created_event() {
        let event = AuditEvent::policy_created(
            "admin",
            "policy-1",
            "AllowReadInstances",
            Scope::org("org-1"),
        );

        match &event.kind {
            AuditEventKind::Policy(data) => {
                assert!(matches!(data.change_type, PolicyChangeType::Created));
                assert_eq!(data.policy_id, "policy-1");
            }
            _ => panic!("Expected Policy event"),
        }
    }

    #[test]
    fn test_event_serialization() {
        let event =
            AuditEvent::authn_success("alice", "jwt").with_metadata("user_agent", "curl/7.68.0");

        let json = event.to_json().unwrap();
        let parsed: AuditEvent = serde_json::from_str(&json).unwrap();

        assert_eq!(parsed.principal_id, event.principal_id);
        assert_eq!(
            parsed.metadata.get("user_agent"),
            Some(&"curl/7.68.0".to_string())
        );
    }
}
