//! Health Monitoring for SSH Connections
//!
//! Tracks and maintains connection health status with two strategies:
//! - Passive health tracking: Based on command execution results
//! - Active health checks: Periodic probes to detect server issues early

use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};

use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};

use super::{ConnectionHealth, SshPoolResult};

// ============================================================================
// Health Check Configuration
// ============================================================================

/// Health check configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheckConfig {
    /// Enable passive health tracking
    #[serde(default = "default_passive_enabled")]
    pub passive_enabled: bool,
    /// Enable active health checks
    #[serde(default = "default_active_enabled")]
    pub active_enabled: bool,
    /// Interval between active health checks (seconds)
    #[serde(default = "default_check_interval")]
    pub check_interval_seconds: u64,
    /// Command to execute for health checks
    #[serde(default = "default_check_command")]
    pub check_command: String,
    /// Timeout for health check command (seconds)
    #[serde(default = "default_check_timeout")]
    pub check_timeout_seconds: u64,
    /// Consecutive failures before marking unhealthy
    #[serde(default = "default_failure_threshold")]
    pub failure_threshold: u32,
    /// Consecutive successes to mark healthy again
    #[serde(default = "default_recovery_threshold")]
    pub recovery_threshold: u32,
}

fn default_passive_enabled() -> bool {
    true
}

fn default_active_enabled() -> bool {
    true
}

fn default_check_interval() -> u64 {
    60
}

fn default_check_command() -> String {
    "echo health_check".to_string()
}

fn default_check_timeout() -> u64 {
    5
}

fn default_failure_threshold() -> u32 {
    3
}

fn default_recovery_threshold() -> u32 {
    2
}

impl Default for HealthCheckConfig {
    fn default() -> Self {
        Self {
            passive_enabled: true,
            active_enabled: true,
            check_interval_seconds: 60,
            check_command: "echo health_check".to_string(),
            check_timeout_seconds: 5,
            failure_threshold: 3,
            recovery_threshold: 2,
        }
    }
}

// ============================================================================
// Health Status Tracking
// ============================================================================

/// Per-host health status
#[derive(Debug, Clone)]
pub struct HostHealthStatus {
    /// Host address
    pub host: String,
    /// Current health status
    pub health: ConnectionHealth,
    /// Consecutive failures
    pub failure_count: u32,
    /// Consecutive successes
    pub success_count: u32,
    /// Last check time
    pub last_check: Instant,
    /// Last successful command
    pub last_success: Option<Instant>,
    /// Total checks performed
    pub total_checks: u64,
    /// Successful checks
    pub successful_checks: u64,
}

impl HostHealthStatus {
    /// Create new health status for host
    pub fn new(host: String) -> Self {
        Self {
            host,
            health: ConnectionHealth::Healthy,
            failure_count: 0,
            success_count: 0,
            last_check: Instant::now(),
            last_success: Some(Instant::now()),
            total_checks: 0,
            successful_checks: 0,
        }
    }

    /// Record successful operation
    pub fn record_success(&mut self) {
        self.failure_count = 0;
        self.success_count += 1;
        self.last_check = Instant::now();
        self.last_success = Some(Instant::now());
        self.total_checks += 1;
        self.successful_checks += 1;
    }

    /// Record failed operation
    pub fn record_failure(&mut self) {
        self.success_count = 0;
        self.failure_count += 1;
        self.last_check = Instant::now();
        self.total_checks += 1;
    }

    /// Get health check success rate
    pub fn success_rate(&self) -> f64 {
        if self.total_checks == 0 {
            100.0
        } else {
            (self.successful_checks as f64 / self.total_checks as f64) * 100.0
        }
    }
}

// ============================================================================
// Health Checker
// ============================================================================

/// Health checker managing passive and active checks
pub struct HealthChecker {
    config: HealthCheckConfig,
    /// Per-host health status: host -> HostHealthStatus
    host_status: Arc<RwLock<HashMap<String, HostHealthStatus>>>,
}

impl HealthChecker {
    /// Create a new health checker
    pub fn new(config: HealthCheckConfig) -> SshPoolResult<Self> {
        Ok(Self {
            config,
            host_status: Arc::new(RwLock::new(HashMap::new())),
        })
    }

    /// Create with default configuration
    pub fn with_defaults() -> SshPoolResult<Self> {
        Self::new(HealthCheckConfig::default())
    }

    /// Record successful command execution (passive check)
    pub async fn record_success(&self, host: &str) {
        let mut status = self.host_status.write().await;
        let entry = status
            .entry(host.to_string())
            .or_insert_with(|| HostHealthStatus::new(host.to_string()));

        entry.record_success();

        // Update health status
        if entry.success_count >= self.config.recovery_threshold
            && entry.health != ConnectionHealth::Healthy
        {
            entry.health = ConnectionHealth::Healthy;
            info!("Host {} recovered to Healthy", host);
        }

        debug!(
            "Recorded success for {} (failures: {}, successes: {})",
            host, entry.failure_count, entry.success_count
        );
    }

    /// Record failed command execution (passive check)
    pub async fn record_failure(&self, host: &str) {
        let mut status = self.host_status.write().await;
        let entry = status
            .entry(host.to_string())
            .or_insert_with(|| HostHealthStatus::new(host.to_string()));

        entry.record_failure();

        // Update health status based on failure count
        match entry.health {
            ConnectionHealth::Healthy => {
                if entry.failure_count >= self.config.failure_threshold {
                    entry.health = ConnectionHealth::Degraded;
                    warn!(
                        "Host {} marked as Degraded after {} failures",
                        host, entry.failure_count
                    );
                }
            }
            ConnectionHealth::Degraded => {
                if entry.failure_count >= self.config.failure_threshold * 2 {
                    entry.health = ConnectionHealth::Unhealthy;
                    warn!(
                        "Host {} marked as Unhealthy after {} failures",
                        host, entry.failure_count
                    );
                }
            }
            _ => {}
        }

        debug!(
            "Recorded failure for {} (failures: {}, health: {:?})",
            host, entry.failure_count, entry.health
        );
    }

    /// Get health status for host
    pub async fn get_status(&self, host: &str) -> Option<ConnectionHealth> {
        let status = self.host_status.read().await;
        status.get(host).map(|s| s.health)
    }

    /// Get detailed health information for host
    pub async fn get_detailed_status(&self, host: &str) -> Option<HostHealthStatus> {
        let status = self.host_status.read().await;
        status.get(host).cloned()
    }

    /// Get all host statuses
    pub async fn get_all_statuses(&self) -> Vec<HostHealthStatus> {
        let status = self.host_status.read().await;
        status.values().cloned().collect()
    }

    /// Reset health status for host
    pub async fn reset(&self, host: &str) {
        let mut status = self.host_status.write().await;
        if let Some(entry) = status.get_mut(host) {
            entry.health = ConnectionHealth::Healthy;
            entry.failure_count = 0;
            entry.success_count = 0;
            entry.last_check = Instant::now();
            info!("Reset health status for {}", host);
        }
    }

    /// Get health check configuration
    pub fn config(&self) -> &HealthCheckConfig {
        &self.config
    }

    /// Get success rate for host
    pub async fn success_rate(&self, host: &str) -> Option<f64> {
        let status = self.host_status.read().await;
        status.get(host).map(|s| s.success_rate())
    }

    /// Get statistics for all hosts
    pub async fn get_statistics(&self) -> HealthCheckStatistics {
        let status = self.host_status.read().await;

        let mut healthy_count = 0;
        let mut degraded_count = 0;
        let mut unhealthy_count = 0;
        let mut total_checks = 0;
        let mut total_successes = 0;

        for host_status in status.values() {
            match host_status.health {
                ConnectionHealth::Healthy => healthy_count += 1,
                ConnectionHealth::Degraded => degraded_count += 1,
                ConnectionHealth::Unhealthy => unhealthy_count += 1,
                ConnectionHealth::Expired => {}
            }
            total_checks += host_status.total_checks;
            total_successes += host_status.successful_checks;
        }

        HealthCheckStatistics {
            total_hosts: status.len(),
            healthy_hosts: healthy_count,
            degraded_hosts: degraded_count,
            unhealthy_hosts: unhealthy_count,
            total_checks,
            successful_checks: total_successes,
        }
    }

    /// Start background health check task
    pub async fn start_active_checks(self: Arc<Self>) {
        tokio::spawn(async move {
            let mut interval =
                tokio::time::interval(Duration::from_secs(self.config.check_interval_seconds));

            loop {
                interval.tick().await;

                // Get all hosts currently being tracked
                let hosts: Vec<String> = {
                    let status = self.host_status.read().await;
                    status.keys().cloned().collect()
                };

                // Perform health checks (in real implementation, would SSH to each host)
                for host in hosts {
                    debug!("Active health check for {}", host);
                    // In production, would execute check_command via SSH
                    // For now, just mark as checked
                    self.record_success(&host).await;
                }
            }
        });
    }
}

impl Clone for HealthChecker {
    fn clone(&self) -> Self {
        Self {
            config: self.config.clone(),
            host_status: Arc::clone(&self.host_status),
        }
    }
}

impl std::fmt::Debug for HealthChecker {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("HealthChecker")
            .field("config", &self.config)
            .finish()
    }
}

// ============================================================================
// Health Statistics
// ============================================================================

/// Overall health check statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheckStatistics {
    /// Total number of hosts being monitored
    pub total_hosts: usize,
    /// Hosts in healthy state
    pub healthy_hosts: usize,
    /// Hosts in degraded state
    pub degraded_hosts: usize,
    /// Hosts in unhealthy state
    pub unhealthy_hosts: usize,
    /// Total health checks performed
    pub total_checks: u64,
    /// Successful health checks
    pub successful_checks: u64,
}

impl HealthCheckStatistics {
    /// Calculate overall success rate
    pub fn success_rate(&self) -> f64 {
        if self.total_checks == 0 {
            100.0
        } else {
            (self.successful_checks as f64 / self.total_checks as f64) * 100.0
        }
    }
}

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

    #[test]
    fn test_health_status_creation() {
        let status = HostHealthStatus::new("localhost".to_string());
        assert_eq!(status.host, "localhost");
        assert_eq!(status.health, ConnectionHealth::Healthy);
        assert_eq!(status.failure_count, 0);
    }

    #[test]
    fn test_health_status_record_operations() {
        let mut status = HostHealthStatus::new("localhost".to_string());

        status.record_success();
        assert_eq!(status.success_count, 1);
        assert_eq!(status.total_checks, 1);
        assert_eq!(status.successful_checks, 1);

        status.record_failure();
        assert_eq!(status.failure_count, 1);
        assert_eq!(status.success_count, 0);
        assert_eq!(status.total_checks, 2);
        assert_eq!(status.successful_checks, 1);
    }

    #[test]
    fn test_health_status_success_rate() {
        let mut status = HostHealthStatus::new("localhost".to_string());

        // 80% success rate: 4 successes, 1 failure
        for _ in 0..4 {
            status.record_success();
        }
        status.record_failure();

        let rate = status.success_rate();
        assert!((rate - 80.0).abs() < 0.1);
    }

    #[tokio::test]
    async fn test_health_checker_passive_checks() {
        let checker = HealthChecker::with_defaults().unwrap();

        checker.record_success("host1").await;
        let status = checker.get_status("host1").await;
        assert_eq!(status, Some(ConnectionHealth::Healthy));

        let detailed = checker.get_detailed_status("host1").await;
        assert!(detailed.is_some());
        assert_eq!(detailed.unwrap().success_count, 1);
    }

    #[tokio::test]
    async fn test_health_checker_degradation() {
        let config = HealthCheckConfig {
            failure_threshold: 2,
            recovery_threshold: 1,
            ..Default::default()
        };
        let checker = HealthChecker::new(config).unwrap();

        // Record failures
        checker.record_failure("host1").await;
        checker.record_failure("host1").await;

        let status = checker.get_status("host1").await;
        assert_eq!(status, Some(ConnectionHealth::Degraded));

        // Record success to recover
        checker.record_success("host1").await;
        let status = checker.get_status("host1").await;
        assert_eq!(status, Some(ConnectionHealth::Healthy));
    }

    #[tokio::test]
    async fn test_health_checker_reset() {
        let checker = HealthChecker::with_defaults().unwrap();

        checker.record_failure("host1").await;
        checker.record_failure("host1").await;
        checker.record_failure("host1").await;

        let detailed = checker.get_detailed_status("host1").await.unwrap();
        assert_eq!(detailed.failure_count, 3);

        checker.reset("host1").await;
        let detailed = checker.get_detailed_status("host1").await.unwrap();
        assert_eq!(detailed.failure_count, 0);
        assert_eq!(detailed.health, ConnectionHealth::Healthy);
    }
}
