//! Multi-Party Approval System
//!
//! Implements the approval workflow for break-glass requests with:
//! - Multi-party approval (minimum 2 approvers)
//! - Different team requirement
//! - Role-based approver validation
//! - Time window enforcement

use std::collections::{HashMap, HashSet};
use std::net::IpAddr;
use std::sync::Arc;

use anyhow::{anyhow, Result};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};

use super::types::*;

/// Approval manager handles the approval workflow
pub struct ApprovalManager {
    /// Pending requests awaiting approval
    requests: Arc<RwLock<HashMap<String, BreakGlassRequest>>>,

    /// Collected approvals per request
    approvals: Arc<RwLock<HashMap<String, Vec<Approval>>>>,

    /// Approval configuration
    config: ApprovalConfig,
}

impl ApprovalManager {
    /// Create new approval manager
    pub fn new(config: ApprovalConfig) -> Self {
        Self {
            requests: Arc::new(RwLock::new(HashMap::new())),
            approvals: Arc::new(RwLock::new(HashMap::new())),
            config,
        }
    }

    /// Submit new break-glass request
    pub async fn submit_request(&self, request: BreakGlassRequest) -> Result<String> {
        let request_id = request.id.clone();

        info!(
            request_id = %request_id,
            requester = %request.requester.email,
            reason = %request.reason,
            "Break-glass request submitted"
        );

        // Store request
        let mut requests = self.requests.write().await;
        requests.insert(request_id.clone(), request);

        // Initialize empty approvals list
        let mut approvals = self.approvals.write().await;
        approvals.insert(request_id.clone(), Vec::new());

        Ok(request_id)
    }

    /// Approve request
    pub async fn approve_request(
        &self,
        request_id: &str,
        approver: User,
        reason: String,
        ip_address: IpAddr,
    ) -> Result<ApprovalResult> {
        // Get request
        let mut requests = self.requests.write().await;
        let request = requests
            .get_mut(request_id)
            .ok_or_else(|| anyhow!("Request {} not found", request_id))?;

        // Validate request can be approved
        if !request.can_approve() {
            return Err(anyhow!(
                "Request cannot be approved (status: {:?}, expired: {})",
                request.status,
                request.is_expired()
            ));
        }

        // Validate approver
        self.validate_approver(&approver, &request.requester)?;

        // Create approval
        let approval = Approval::new(approver.clone(), reason, ip_address);

        // Add approval
        let mut approvals = self.approvals.write().await;
        let request_approvals = approvals
            .get_mut(request_id)
            .ok_or_else(|| anyhow!("Approvals not found for request {}", request_id))?;

        request_approvals.push(approval);

        info!(
            request_id = %request_id,
            approver = %approver.email,
            approval_count = request_approvals.len(),
            "Approval granted"
        );

        // Check if we have enough approvals
        if self.has_sufficient_approvals(request_approvals)? {
            request.status = RequestStatus::Approved;

            info!(
                request_id = %request_id,
                "Request fully approved, ready for activation"
            );

            Ok(ApprovalResult::Approved {
                request_id: request_id.to_string(),
                approvals: request_approvals.clone(),
            })
        } else {
            let remaining = self.config.min_approvals as usize - request_approvals.len();
            debug!(
                request_id = %request_id,
                remaining = remaining,
                "Approval recorded, waiting for more approvals"
            );

            Ok(ApprovalResult::PartiallyApproved {
                request_id: request_id.to_string(),
                current_approvals: request_approvals.len() as u32,
                required_approvals: self.config.min_approvals,
            })
        }
    }

    /// Deny request
    pub async fn deny_request(&self, request_id: &str, denier: User, reason: String) -> Result<()> {
        let mut requests = self.requests.write().await;
        let request = requests
            .get_mut(request_id)
            .ok_or_else(|| anyhow!("Request {} not found", request_id))?;

        request.status = RequestStatus::Denied;

        warn!(
            request_id = %request_id,
            denier = %denier.email,
            reason = %reason,
            "Request denied"
        );

        Ok(())
    }

    /// Get request with approvals
    pub async fn get_request(
        &self,
        request_id: &str,
    ) -> Result<(BreakGlassRequest, Vec<Approval>)> {
        let requests = self.requests.read().await;
        let request = requests
            .get(request_id)
            .ok_or_else(|| anyhow!("Request {} not found", request_id))?
            .clone();

        let approvals = self.approvals.read().await;
        let request_approvals = approvals.get(request_id).cloned().unwrap_or_default();

        Ok((request, request_approvals))
    }

    /// List all pending requests
    pub async fn list_pending_requests(&self) -> Vec<BreakGlassRequest> {
        let requests = self.requests.read().await;
        requests
            .values()
            .filter(|r| r.status == RequestStatus::PendingApproval && !r.is_expired())
            .cloned()
            .collect()
    }

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

    /// Clean up expired requests
    pub async fn cleanup_expired(&self) -> usize {
        let mut requests = self.requests.write().await;
        let mut approvals = self.approvals.write().await;

        let expired: Vec<String> = requests
            .iter_mut()
            .filter_map(|(id, req)| {
                if req.status == RequestStatus::PendingApproval && req.is_expired() {
                    req.status = RequestStatus::Expired;
                    Some(id.clone())
                } else {
                    None
                }
            })
            .collect();

        let count = expired.len();

        // Remove approvals for expired requests
        for id in expired {
            approvals.remove(&id);
        }

        if count > 0 {
            info!(expired_count = count, "Cleaned up expired requests");
        }

        count
    }

    /// Validate approver
    fn validate_approver(&self, approver: &User, requester: &User) -> Result<()> {
        // Cannot approve own request
        if approver.id == requester.id {
            return Err(anyhow!("Cannot approve your own request"));
        }

        // Check if approver has required role
        let has_required_role = approver
            .roles
            .iter()
            .any(|role| self.config.approver_roles.contains(role));

        if !has_required_role {
            return Err(anyhow!(
                "Approver does not have required role. Required: {:?}",
                self.config.approver_roles
            ));
        }

        Ok(())
    }

    /// Check if we have sufficient approvals
    fn has_sufficient_approvals(&self, approvals: &[Approval]) -> Result<bool> {
        // Check minimum count
        if approvals.len() < self.config.min_approvals as usize {
            return Ok(false);
        }

        // Check different teams requirement
        if self.config.require_different_teams {
            let teams: HashSet<_> = approvals
                .iter()
                .flat_map(|a| a.approver.teams.iter())
                .collect();

            if teams.len() < self.config.min_approvals as usize {
                debug!(
                    "Insufficient team diversity: {} teams, {} required",
                    teams.len(),
                    self.config.min_approvals
                );
                return Ok(false);
            }
        }

        Ok(true)
    }

    /// Get approval statistics
    pub async fn get_statistics(&self) -> ApprovalStatistics {
        let requests = self.requests.read().await;

        let total = requests.len();
        let pending = requests
            .values()
            .filter(|r| r.status == RequestStatus::PendingApproval)
            .count();
        let approved = requests
            .values()
            .filter(|r| r.status == RequestStatus::Approved)
            .count();
        let denied = requests
            .values()
            .filter(|r| r.status == RequestStatus::Denied)
            .count();
        let expired = requests
            .values()
            .filter(|r| r.status == RequestStatus::Expired)
            .count();

        ApprovalStatistics {
            total_requests: total,
            pending_requests: pending,
            approved_requests: approved,
            denied_requests: denied,
            expired_requests: expired,
        }
    }
}

/// Approval result
#[derive(Debug, Clone)]
pub enum ApprovalResult {
    /// Request fully approved and ready for activation
    Approved {
        request_id: String,
        approvals: Vec<Approval>,
    },

    /// Request partially approved, waiting for more approvals
    PartiallyApproved {
        request_id: String,
        current_approvals: u32,
        required_approvals: u32,
    },
}

/// Approval statistics
#[derive(Debug, Clone, serde::Serialize)]
pub struct ApprovalStatistics {
    pub total_requests: usize,
    pub pending_requests: usize,
    pub approved_requests: usize,
    pub denied_requests: usize,
    pub expired_requests: usize,
}

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

    use chrono::{Duration, Utc};

    use super::*;

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

    fn create_test_user(id: &str, team: &str, role: Role) -> User {
        User {
            id: id.to_string(),
            email: format!("{}@example.com", id),
            name: id.to_string(),
            teams: vec![team.to_string()],
            roles: vec![role],
        }
    }

    #[tokio::test]
    async fn test_approval_workflow() {
        let config = create_test_config();
        let manager = ApprovalManager::new(config);

        // Create request
        let requester = create_test_user("requester", "dev", Role::Developer);
        let request = BreakGlassRequest::new(
            requester.clone(),
            "Emergency".to_string(),
            "Production issue".to_string(),
            vec![],
            vec![],
            Duration::hours(2),
        );

        let request_id = manager.submit_request(request).await.unwrap();

        // First approval
        let approver1 = create_test_user("approver1", "security", Role::SecurityOfficer);
        let result = manager
            .approve_request(
                &request_id,
                approver1,
                "Approved".to_string(),
                IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
            )
            .await
            .unwrap();

        assert!(matches!(result, ApprovalResult::PartiallyApproved { .. }));

        // Second approval from different team
        let approver2 = create_test_user("approver2", "ops", Role::Admin);
        let result = manager
            .approve_request(
                &request_id,
                approver2,
                "Approved".to_string(),
                IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
            )
            .await
            .unwrap();

        assert!(matches!(result, ApprovalResult::Approved { .. }));
    }

    #[tokio::test]
    async fn test_self_approval_rejected() {
        let config = create_test_config();
        let manager = ApprovalManager::new(config);

        let requester = create_test_user("user1", "security", Role::SecurityOfficer);
        let request = BreakGlassRequest::new(
            requester.clone(),
            "Emergency".to_string(),
            "Test".to_string(),
            vec![],
            vec![],
            Duration::hours(1),
        );

        let request_id = manager.submit_request(request).await.unwrap();

        // Try to approve own request
        let result = manager
            .approve_request(
                &request_id,
                requester,
                "Self approval".to_string(),
                IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
            )
            .await;

        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("Cannot approve your own request"));
    }

    #[tokio::test]
    async fn test_insufficient_role() {
        let config = create_test_config();
        let manager = ApprovalManager::new(config);

        let requester = create_test_user("requester", "dev", Role::Developer);
        let request = BreakGlassRequest::new(
            requester,
            "Emergency".to_string(),
            "Test".to_string(),
            vec![],
            vec![],
            Duration::hours(1),
        );

        let request_id = manager.submit_request(request).await.unwrap();

        // Try to approve with insufficient role
        let approver = create_test_user("approver", "dev", Role::Developer);
        let result = manager
            .approve_request(
                &request_id,
                approver,
                "Approved".to_string(),
                IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
            )
            .await;

        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("does not have required role"));
    }

    #[tokio::test]
    async fn test_cleanup_expired() {
        let config = create_test_config();
        let manager = ApprovalManager::new(config);

        // Create expired request
        let requester = create_test_user("user", "dev", Role::Developer);
        let mut request = BreakGlassRequest::new(
            requester,
            "Emergency".to_string(),
            "Test".to_string(),
            vec![],
            vec![],
            Duration::hours(1),
        );

        // Make it expired
        request.expires_at = Utc::now() - Duration::hours(1);
        manager.submit_request(request).await.unwrap();

        let cleaned = manager.cleanup_expired().await;
        assert_eq!(cleaned, 1);
    }
}
