//! Emergency Session Management
//!
//! Manages active break-glass sessions including:
//! - Session activation
//! - Token generation
//! - Activity tracking
//! - Action logging

use std::collections::HashMap;
use std::sync::Arc;

use anyhow::{anyhow, Result};
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};

use super::types::*;

/// Session manager handles active emergency sessions
pub struct SessionManager {
    /// Active sessions
    sessions: Arc<RwLock<HashMap<String, BreakGlassSession>>>,

    /// JWT encoding key for emergency tokens
    encoding_key: Option<EncodingKey>,

    /// Configuration
    config: BreakGlassConfig,
}

impl SessionManager {
    /// Create new session manager
    pub fn new(config: BreakGlassConfig) -> Self {
        Self {
            sessions: Arc::new(RwLock::new(HashMap::new())),
            encoding_key: None,
            config,
        }
    }

    /// Set JWT encoding key
    pub fn with_encoding_key(mut self, key: EncodingKey) -> Self {
        self.encoding_key = Some(key);
        self
    }

    /// Create session from approved request
    pub async fn create_session(
        &self,
        request: BreakGlassRequest,
        approvals: Vec<Approval>,
    ) -> Result<String> {
        // Validate request is approved
        if request.status != RequestStatus::Approved {
            return Err(anyhow!("Request must be approved before creating session"));
        }

        // Validate approvals
        if approvals.is_empty() {
            return Err(anyhow!("No approvals provided"));
        }

        // Create session
        let session = BreakGlassSession::from_request(request, approvals);
        let session_id = session.id.clone();

        info!(
            session_id = %session_id,
            requester = %session.request.requester.email,
            "Emergency session created"
        );

        // Store session
        let mut sessions = self.sessions.write().await;
        sessions.insert(session_id.clone(), session);

        Ok(session_id)
    }

    /// Activate session and generate emergency token
    pub async fn activate_session(&self, session_id: &str) -> Result<EmergencyAccessToken> {
        let mut sessions = self.sessions.write().await;
        let session = sessions
            .get_mut(session_id)
            .ok_or_else(|| anyhow!("Session {} not found", session_id))?;

        // Validate session can be activated
        if session.status != SessionStatus::Approved {
            return Err(anyhow!(
                "Session cannot be activated (status: {:?})",
                session.status
            ));
        }

        // Generate emergency token
        let token = self.generate_emergency_token(session)?;

        // Activate session
        session.activate(token.access_token.clone());

        info!(
            session_id = %session_id,
            expires_at = %token.expires_at,
            "Emergency session activated"
        );

        Ok(token)
    }

    /// Record action in session
    pub async fn record_action(&self, session_id: &str, action: EmergencyAction) -> Result<()> {
        let mut sessions = self.sessions.write().await;
        let session = sessions
            .get_mut(session_id)
            .ok_or_else(|| anyhow!("Session {} not found", session_id))?;

        // Validate session is active
        if !session.is_active() {
            return Err(anyhow!("Session is not active"));
        }

        debug!(
            session_id = %session_id,
            action_type = %action.action_type,
            resource = %action.resource,
            "Recording emergency action"
        );

        session.add_action(action);

        Ok(())
    }

    /// Revoke session
    pub async fn revoke_session(&self, session_id: &str, reason: String) -> Result<()> {
        let mut sessions = self.sessions.write().await;
        let session = sessions
            .get_mut(session_id)
            .ok_or_else(|| anyhow!("Session {} not found", session_id))?;

        warn!(
            session_id = %session_id,
            reason = %reason,
            "Revoking emergency session"
        );

        session.revoke(reason);

        Ok(())
    }

    /// Get session
    pub async fn get_session(&self, session_id: &str) -> Result<BreakGlassSession> {
        let sessions = self.sessions.read().await;
        sessions
            .get(session_id)
            .cloned()
            .ok_or_else(|| anyhow!("Session {} not found", session_id))
    }

    /// List active sessions
    pub async fn list_active_sessions(&self) -> Vec<BreakGlassSession> {
        let sessions = self.sessions.read().await;
        sessions
            .values()
            .filter(|s| s.is_active())
            .cloned()
            .collect()
    }

    /// List all sessions
    pub async fn list_all_sessions(&self) -> Vec<BreakGlassSession> {
        let sessions = self.sessions.read().await;
        sessions.values().cloned().collect()
    }

    /// Check if session is valid
    pub async fn validate_session(&self, session_id: &str) -> Result<bool> {
        let session = self.get_session(session_id).await?;
        Ok(session.is_active())
    }

    /// Update session activity
    pub async fn update_activity(&self, session_id: &str) -> Result<()> {
        let mut sessions = self.sessions.write().await;
        let session = sessions
            .get_mut(session_id)
            .ok_or_else(|| anyhow!("Session {} not found", session_id))?;

        session.record_activity();

        Ok(())
    }

    /// Get sessions requiring auto-revocation
    pub async fn get_revocable_sessions(&self) -> Vec<(String, String)> {
        let sessions = self.sessions.read().await;
        let mut revocable = Vec::new();

        for (id, session) in sessions.iter() {
            if session.status != SessionStatus::Active {
                continue;
            }

            if session.is_expired() {
                revocable.push((id.clone(), "Session expired".to_string()));
            } else if session.is_inactive(self.config.inactivity_timeout_minutes) {
                revocable.push((
                    id.clone(),
                    format!(
                        "Inactive for {} minutes",
                        self.config.inactivity_timeout_minutes
                    ),
                ));
            }
        }

        revocable
    }

    /// Get session statistics
    pub async fn get_statistics(&self) -> SessionStatistics {
        let sessions = self.sessions.read().await;

        let total = sessions.len();
        let active = sessions
            .values()
            .filter(|s| s.status == SessionStatus::Active)
            .count();
        let revoked = sessions
            .values()
            .filter(|s| s.status == SessionStatus::Revoked)
            .count();
        let expired = sessions
            .values()
            .filter(|s| s.status == SessionStatus::Expired)
            .count();

        let total_actions: usize = sessions.values().map(|s| s.actions.len()).sum();

        SessionStatistics {
            total_sessions: total,
            active_sessions: active,
            revoked_sessions: revoked,
            expired_sessions: expired,
            total_actions,
        }
    }

    /// Generate emergency access token
    fn generate_emergency_token(
        &self,
        session: &BreakGlassSession,
    ) -> Result<EmergencyAccessToken> {
        let audit_id = uuid::Uuid::new_v4().to_string();
        let max_duration = Duration::hours(self.config.max_session_duration_hours);
        let expires_at = Utc::now() + max_duration;

        // Extract approver IDs
        let approved_by: Vec<String> = session
            .approvals
            .iter()
            .map(|a| a.approver.id.clone())
            .collect();

        // Build JWT claims
        let claims = EmergencyTokenClaims {
            jti: uuid::Uuid::new_v4().to_string(),
            sub: session.request.requester.id.clone(),
            session_id: session.id.clone(),
            emergency_access: true,
            approved_by,
            reason: session.request.reason.clone(),
            permissions: session.request.requested_permissions.clone(),
            iat: Utc::now().timestamp(),
            exp: expires_at.timestamp(),
            iss: "orchestrator-break-glass".to_string(),
            aud: vec!["orchestrator".to_string()],
            audit_id: audit_id.clone(),
        };

        // Encode JWT
        let token = if let Some(ref key) = self.encoding_key {
            let header = Header::new(Algorithm::RS256);
            encode(&header, &claims, key).map_err(|e| anyhow!("Failed to encode JWT: {}", e))?
        } else {
            // For testing without key
            "emergency_token_placeholder".to_string()
        };

        Ok(EmergencyAccessToken {
            access_token: token,
            session_id: session.id.clone(),
            expires_at,
            max_duration,
            permissions: session.request.requested_permissions.clone(),
            audit_id,
        })
    }
}

/// Emergency JWT token claims
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct EmergencyTokenClaims {
    /// Token ID
    jti: String,

    /// Subject (user ID)
    sub: String,

    /// Session ID
    session_id: String,

    /// Emergency access flag
    emergency_access: bool,

    /// Approver IDs
    approved_by: Vec<String>,

    /// Emergency reason
    reason: String,

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

    /// Issued at
    iat: i64,

    /// Expiration
    exp: i64,

    /// Issuer
    iss: String,

    /// Audience
    aud: Vec<String>,

    /// Audit ID
    audit_id: String,
}

/// Session statistics
#[derive(Debug, Clone, serde::Serialize)]
pub struct SessionStatistics {
    pub total_sessions: usize,
    pub active_sessions: usize,
    pub revoked_sessions: usize,
    pub expired_sessions: usize,
    pub total_actions: usize,
}

#[cfg(test)]
mod tests {
    use std::net::{IpAddr, 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],
        }
    }

    fn create_approved_request() -> BreakGlassRequest {
        let mut request = BreakGlassRequest::new(
            create_test_user(),
            "Emergency".to_string(),
            "Production outage".to_string(),
            vec!["database/*".to_string()],
            vec![Permission {
                resource: "database".to_string(),
                action: "admin".to_string(),
                scope: Some("production".to_string()),
            }],
            Duration::hours(2),
        );
        request.status = RequestStatus::Approved;
        request
    }

    #[tokio::test]
    async fn test_session_creation() {
        let config = BreakGlassConfig::default();
        let manager = SessionManager::new(config);

        let request = create_approved_request();
        let approval = Approval::new(
            create_test_user(),
            "Approved".to_string(),
            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
        );

        let session_id = manager
            .create_session(request, vec![approval])
            .await
            .unwrap();

        assert!(!session_id.is_empty());

        let session = manager.get_session(&session_id).await.unwrap();
        assert_eq!(session.status, SessionStatus::Approved);
    }

    #[tokio::test]
    async fn test_session_activation() {
        let config = BreakGlassConfig::default();
        let manager = SessionManager::new(config);

        let request = create_approved_request();
        let approval = Approval::new(
            create_test_user(),
            "Approved".to_string(),
            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
        );

        let session_id = manager
            .create_session(request, vec![approval])
            .await
            .unwrap();

        let token = manager.activate_session(&session_id).await.unwrap();

        assert!(!token.access_token.is_empty());
        assert_eq!(token.session_id, session_id);

        let session = manager.get_session(&session_id).await.unwrap();
        assert_eq!(session.status, SessionStatus::Active);
        assert!(session.is_active());
    }

    #[tokio::test]
    async fn test_action_recording() {
        let config = BreakGlassConfig::default();
        let manager = SessionManager::new(config);

        let request = create_approved_request();
        let approval = Approval::new(
            create_test_user(),
            "Approved".to_string(),
            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
        );

        let session_id = manager
            .create_session(request, vec![approval])
            .await
            .unwrap();

        manager.activate_session(&session_id).await.unwrap();

        let action = EmergencyAction::new(
            "database_restart".to_string(),
            "postgres-prod".to_string(),
            serde_json::json!({}),
        );

        manager.record_action(&session_id, action).await.unwrap();

        let session = manager.get_session(&session_id).await.unwrap();
        assert_eq!(session.actions.len(), 1);
    }

    #[tokio::test]
    async fn test_session_revocation() {
        let config = BreakGlassConfig::default();
        let manager = SessionManager::new(config);

        let request = create_approved_request();
        let approval = Approval::new(
            create_test_user(),
            "Approved".to_string(),
            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
        );

        let session_id = manager
            .create_session(request, vec![approval])
            .await
            .unwrap();

        manager.activate_session(&session_id).await.unwrap();

        manager
            .revoke_session(&session_id, "Manual revocation".to_string())
            .await
            .unwrap();

        let session = manager.get_session(&session_id).await.unwrap();
        assert_eq!(session.status, SessionStatus::Revoked);
        assert!(!session.is_active());
    }

    #[tokio::test]
    async fn test_list_active_sessions() {
        let config = BreakGlassConfig::default();
        let manager = SessionManager::new(config);

        // Create and activate session
        let request = create_approved_request();
        let approval = Approval::new(
            create_test_user(),
            "Approved".to_string(),
            IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
        );

        let session_id = manager
            .create_session(request, vec![approval])
            .await
            .unwrap();

        manager.activate_session(&session_id).await.unwrap();

        let active = manager.list_active_sessions().await;
        assert_eq!(active.len(), 1);
        assert_eq!(active[0].id, session_id);
    }
}
