//! Break-Glass Type Definitions

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

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

/// Break-glass emergency request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakGlassRequest {
    /// Unique request ID
    pub id: String,

    /// User requesting emergency access
    pub requester: User,

    /// Emergency reason (brief)
    pub reason: String,

    /// Detailed justification
    pub justification: String,

    /// Target resources requiring access
    pub target_resources: Vec<String>,

    /// Requested permissions
    pub requested_permissions: Vec<Permission>,

    /// Requested duration
    pub duration: Duration,

    /// Request creation timestamp
    pub created_at: DateTime<Utc>,

    /// Request expiration (approval window)
    pub expires_at: DateTime<Utc>,

    /// Request status
    pub status: RequestStatus,
}

impl BreakGlassRequest {
    /// Create new break-glass request
    pub fn new(
        requester: User,
        reason: String,
        justification: String,
        target_resources: Vec<String>,
        requested_permissions: Vec<Permission>,
        duration: Duration,
    ) -> Self {
        let id = Uuid::new_v4().to_string();
        let created_at = Utc::now();
        let expires_at = created_at + Duration::hours(1); // 1 hour approval window

        Self {
            id,
            requester,
            reason,
            justification,
            target_resources,
            requested_permissions,
            duration,
            created_at,
            expires_at,
            status: RequestStatus::PendingApproval,
        }
    }

    /// Check if request is expired
    pub fn is_expired(&self) -> bool {
        Utc::now() > self.expires_at
    }

    /// Check if request can be approved
    pub fn can_approve(&self) -> bool {
        self.status == RequestStatus::PendingApproval && !self.is_expired()
    }
}

/// Break-glass session (approved and active)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakGlassSession {
    /// Unique session ID
    pub id: String,

    /// Original request
    pub request: BreakGlassRequest,

    /// Approvals received
    pub approvals: Vec<Approval>,

    /// Session status
    pub status: SessionStatus,

    /// Session activation timestamp
    pub activated_at: Option<DateTime<Utc>>,

    /// Session expiration timestamp
    pub expires_at: Option<DateTime<Utc>>,

    /// Last activity timestamp
    pub last_activity_at: Option<DateTime<Utc>>,

    /// Revocation timestamp
    pub revoked_at: Option<DateTime<Utc>>,

    /// Revocation reason
    pub revocation_reason: Option<String>,

    /// Emergency access token
    pub access_token: Option<String>,

    /// Actions performed during session
    pub actions: Vec<EmergencyAction>,
}

impl BreakGlassSession {
    /// Create session from approved request
    pub fn from_request(request: BreakGlassRequest, approvals: Vec<Approval>) -> Self {
        let id = Uuid::new_v4().to_string();

        Self {
            id,
            request,
            approvals,
            status: SessionStatus::Approved,
            activated_at: None,
            expires_at: None,
            last_activity_at: None,
            revoked_at: None,
            revocation_reason: None,
            access_token: None,
            actions: Vec::new(),
        }
    }

    /// Activate session
    pub fn activate(&mut self, token: String) {
        let now = Utc::now();
        self.status = SessionStatus::Active;
        self.activated_at = Some(now);
        self.expires_at = Some(now + self.request.duration);
        self.last_activity_at = Some(now);
        self.access_token = Some(token);
    }

    /// Record activity
    pub fn record_activity(&mut self) {
        self.last_activity_at = Some(Utc::now());
    }

    /// Add action
    pub fn add_action(&mut self, action: EmergencyAction) {
        self.actions.push(action);
        self.record_activity();
    }

    /// Revoke session
    pub fn revoke(&mut self, reason: String) {
        self.status = SessionStatus::Revoked;
        self.revoked_at = Some(Utc::now());
        self.revocation_reason = Some(reason);
        self.access_token = None;
    }

    /// Check if session is expired
    pub fn is_expired(&self) -> bool {
        if let Some(expires_at) = self.expires_at {
            Utc::now() > expires_at
        } else {
            false
        }
    }

    /// Check if session is inactive
    pub fn is_inactive(&self, timeout_minutes: i64) -> bool {
        if let Some(last_activity) = self.last_activity_at {
            let inactive_duration = Utc::now() - last_activity;
            inactive_duration > Duration::minutes(timeout_minutes)
        } else {
            false
        }
    }

    /// Check if session is active
    pub fn is_active(&self) -> bool {
        self.status == SessionStatus::Active && !self.is_expired()
    }
}

/// User information
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct User {
    /// User ID
    pub id: String,

    /// User email
    pub email: String,

    /// User name
    pub name: String,

    /// User teams
    pub teams: Vec<String>,

    /// User roles
    pub roles: Vec<Role>,
}

/// User role
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Role {
    Admin,
    SecurityOfficer,
    Developer,
    Operator,
    Custom(String),
}

/// Permission definition
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Permission {
    /// Resource type
    pub resource: String,

    /// Action
    pub action: String,

    /// Scope (optional)
    pub scope: Option<String>,
}

/// Approval from authorized user
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Approval {
    /// Approval ID
    pub id: String,

    /// Approver user
    pub approver: User,

    /// Approval timestamp
    pub approved_at: DateTime<Utc>,

    /// Approval reason/justification
    pub approval_reason: String,

    /// Approver IP address
    pub ip_address: IpAddr,

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

impl Approval {
    /// Create new approval
    pub fn new(approver: User, reason: String, ip_address: IpAddr) -> Self {
        Self {
            id: Uuid::new_v4().to_string(),
            approver,
            approved_at: Utc::now(),
            approval_reason: reason,
            ip_address,
            metadata: HashMap::new(),
        }
    }

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

/// Emergency action performed during session
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmergencyAction {
    /// Action ID
    pub id: String,

    /// Action type
    pub action_type: String,

    /// Target resource
    pub resource: String,

    /// Action parameters
    pub parameters: serde_json::Value,

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

    /// Result status
    pub status: ActionStatus,

    /// Result details
    pub result: Option<String>,

    /// Error details (if failed)
    pub error: Option<String>,
}

impl EmergencyAction {
    /// Create new emergency action
    pub fn new(action_type: String, resource: String, parameters: serde_json::Value) -> Self {
        Self {
            id: Uuid::new_v4().to_string(),
            action_type,
            resource,
            parameters,
            timestamp: Utc::now(),
            status: ActionStatus::Pending,
            result: None,
            error: None,
        }
    }

    /// Mark as completed
    pub fn complete(mut self, result: String) -> Self {
        self.status = ActionStatus::Completed;
        self.result = Some(result);
        self
    }

    /// Mark as failed
    pub fn fail(mut self, error: String) -> Self {
        self.status = ActionStatus::Failed;
        self.error = Some(error);
        self
    }
}

/// Request status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RequestStatus {
    PendingApproval,
    Approved,
    Denied,
    Expired,
}

/// Session status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SessionStatus {
    Approved,
    Active,
    Expired,
    Revoked,
}

/// Action status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ActionStatus {
    Pending,
    Completed,
    Failed,
}

/// Emergency access token (JWT with special claims)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmergencyAccessToken {
    /// Access token (JWT)
    pub access_token: String,

    /// Session ID
    pub session_id: String,

    /// Token expiration
    pub expires_at: DateTime<Utc>,

    /// Maximum duration
    pub max_duration: Duration,

    /// Granted permissions
    pub permissions: Vec<Permission>,

    /// Audit ID
    pub audit_id: String,
}

/// Break-glass configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakGlassConfig {
    /// Approval configuration
    pub approval_config: ApprovalConfig,

    /// Auto-revocation configuration
    pub auto_revoke_config: AutoRevokeConfig,

    /// Maximum session duration (hours)
    pub max_session_duration_hours: i64,

    /// Inactivity timeout (minutes)
    pub inactivity_timeout_minutes: i64,

    /// Audit retention period (years)
    pub audit_retention_years: i64,
}

impl Default for BreakGlassConfig {
    fn default() -> Self {
        Self {
            approval_config: ApprovalConfig::default(),
            auto_revoke_config: AutoRevokeConfig::default(),
            max_session_duration_hours: 4,
            inactivity_timeout_minutes: 30,
            audit_retention_years: 7,
        }
    }
}

/// Approval configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApprovalConfig {
    /// Minimum number of approvals required
    pub min_approvals: u32,

    /// Require approvers from different teams
    pub require_different_teams: bool,

    /// Approval timeout (hours)
    pub approval_timeout_hours: i64,

    /// Allowed approver roles
    pub approver_roles: Vec<Role>,
}

impl Default for ApprovalConfig {
    fn default() -> Self {
        Self {
            min_approvals: 2,
            require_different_teams: true,
            approval_timeout_hours: 1,
            approver_roles: vec![Role::Admin, Role::SecurityOfficer],
        }
    }
}

/// Auto-revocation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoRevokeConfig {
    /// Check interval (seconds)
    pub check_interval_seconds: u64,

    /// Enable auto-revocation
    pub enabled: bool,

    /// Send alerts on auto-revocation
    pub send_alerts: bool,
}

impl Default for AutoRevokeConfig {
    fn default() -> Self {
        Self {
            check_interval_seconds: 60,
            enabled: true,
            send_alerts: true,
        }
    }
}

/// Break-glass audit event
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BreakGlassAuditEvent {
    /// Event ID
    pub event_id: String,

    /// Event type
    pub event_type: BreakGlassEventType,

    /// Session ID
    pub session_id: String,

    /// User performing action
    pub user: User,

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

    /// Event details
    pub details: serde_json::Value,

    /// IP address
    pub ip_address: IpAddr,

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

impl BreakGlassAuditEvent {
    /// Create new audit event
    pub fn new(
        event_type: BreakGlassEventType,
        session_id: String,
        user: User,
        ip_address: IpAddr,
        details: serde_json::Value,
    ) -> Self {
        Self {
            event_id: Uuid::new_v4().to_string(),
            event_type,
            session_id,
            user,
            timestamp: Utc::now(),
            details,
            ip_address,
            metadata: HashMap::new(),
        }
    }

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

/// Break-glass event types
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BreakGlassEventType {
    RequestCreated,
    ApprovalGranted,
    ApprovalDenied,
    SessionActivated,
    ActionPerformed,
    SessionRevoked,
    SessionExpired,
    SuspiciousActivity,
    AutoRevocation,
}

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

    use super::*;

    fn create_test_user() -> User {
        User {
            id: "user123".to_string(),
            email: "user@example.com".to_string(),
            name: "Test User".to_string(),
            teams: vec!["security".to_string()],
            roles: vec![Role::SecurityOfficer],
        }
    }

    #[test]
    fn test_break_glass_request_creation() {
        let user = create_test_user();
        let request = BreakGlassRequest::new(
            user,
            "Production outage".to_string(),
            "Database cluster is down, need emergency access".to_string(),
            vec!["database/*".to_string()],
            vec![Permission {
                resource: "database".to_string(),
                action: "admin".to_string(),
                scope: Some("production".to_string()),
            }],
            Duration::hours(2),
        );

        assert_eq!(request.status, RequestStatus::PendingApproval);
        assert!(request.can_approve());
        assert!(!request.is_expired());
    }

    #[test]
    fn test_session_lifecycle() {
        let user = create_test_user();
        let request = BreakGlassRequest::new(
            user.clone(),
            "Emergency".to_string(),
            "Testing".to_string(),
            vec![],
            vec![],
            Duration::hours(1),
        );

        let approval = Approval::new(
            user,
            "Approved for testing".to_string(),
            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
        );

        let mut session = BreakGlassSession::from_request(request, vec![approval]);
        assert_eq!(session.status, SessionStatus::Approved);
        assert!(!session.is_active());

        session.activate("test_token".to_string());
        assert_eq!(session.status, SessionStatus::Active);
        assert!(session.is_active());
        assert!(session.access_token.is_some());

        session.revoke("Test revocation".to_string());
        assert_eq!(session.status, SessionStatus::Revoked);
        assert!(!session.is_active());
        assert!(session.access_token.is_none());
    }

    #[test]
    fn test_inactivity_detection() {
        let user = create_test_user();
        let request = BreakGlassRequest::new(
            user,
            "Test".to_string(),
            "Test".to_string(),
            vec![],
            vec![],
            Duration::hours(1),
        );

        let mut session = BreakGlassSession::from_request(request, vec![]);
        session.activate("token".to_string());

        // Simulate old activity
        session.last_activity_at = Some(Utc::now() - Duration::minutes(45));

        assert!(session.is_inactive(30));
        assert!(!session.is_inactive(60));
    }

    #[test]
    fn test_emergency_action() {
        let action = EmergencyAction::new(
            "database_restart".to_string(),
            "postgres-prod-01".to_string(),
            serde_json::json!({"force": true}),
        );

        assert_eq!(action.status, ActionStatus::Pending);

        let completed = action.clone().complete("Success".to_string());
        assert_eq!(completed.status, ActionStatus::Completed);
        assert!(completed.result.is_some());

        let failed = action.fail("Connection timeout".to_string());
        assert_eq!(failed.status, ActionStatus::Failed);
        assert!(failed.error.is_some());
    }
}
