//! Advanced installation features
//!
//! This module provides comprehensive installation features including:
//! - System requirements checking
//! - Version availability verification
//! - Progress reporting
//! - Rollback on failure
//! - Upgrade/downgrade support

use crate::config::PostgresConfig;
use crate::error::{Error, Result};
use lmrc_ssh::SshClient;
use tracing::{debug, info, warn};

/// Platform information
#[derive(Debug, Clone, PartialEq)]
pub enum Platform {
    /// Debian-based systems
    Debian,
    /// Ubuntu-based systems
    Ubuntu,
    /// RedHat-based systems
    RedHat,
    /// CentOS
    CentOS,
    /// Alpine Linux
    Alpine,
    /// Unknown platform
    Unknown(String),
}

impl Platform {
    /// Get platform name as string
    pub fn as_str(&self) -> &str {
        match self {
            Platform::Debian => "Debian",
            Platform::Ubuntu => "Ubuntu",
            Platform::RedHat => "RedHat",
            Platform::CentOS => "CentOS",
            Platform::Alpine => "Alpine",
            Platform::Unknown(s) => s,
        }
    }

    /// Check if platform is supported
    pub fn is_supported(&self) -> bool {
        matches!(self, Platform::Debian | Platform::Ubuntu)
    }
}

/// System requirements
#[derive(Debug, Clone)]
pub struct SystemRequirements {
    /// Minimum RAM in MB
    pub min_ram_mb: u64,
    /// Minimum free disk space in MB
    pub min_disk_mb: u64,
    /// Minimum CPU cores
    pub min_cpu_cores: u32,
}

impl Default for SystemRequirements {
    fn default() -> Self {
        Self {
            min_ram_mb: 1024,  // 1GB RAM
            min_disk_mb: 5120, // 5GB disk
            min_cpu_cores: 1,
        }
    }
}

/// System information
#[derive(Debug, Clone)]
pub struct SystemInfo {
    /// Platform
    pub platform: Platform,
    /// OS version
    pub os_version: String,
    /// Total RAM in MB
    pub total_ram_mb: u64,
    /// Free disk space in MB
    pub free_disk_mb: u64,
    /// Number of CPU cores
    pub cpu_cores: u32,
}

/// Installation progress callback
pub type ProgressCallback = Box<dyn Fn(InstallationStep, u8) + Send + Sync>;

/// Installation step
#[derive(Debug, Clone, PartialEq)]
pub enum InstallationStep {
    /// Checking system requirements
    CheckingRequirements,
    /// Detecting platform
    DetectingPlatform,
    /// Checking version availability
    CheckingVersionAvailability,
    /// Installing prerequisites
    InstallingPrerequisites,
    /// Adding repository
    AddingRepository,
    /// Updating package list
    UpdatingPackages,
    /// Installing PostgreSQL
    InstallingPostgres,
    /// Starting service
    StartingService,
    /// Verifying installation
    VerifyingInstallation,
    /// Installation complete
    Complete,
}

impl InstallationStep {
    /// Get step description
    pub fn description(&self) -> &str {
        match self {
            Self::CheckingRequirements => "Checking system requirements",
            Self::DetectingPlatform => "Detecting platform",
            Self::CheckingVersionAvailability => "Checking PostgreSQL version availability",
            Self::InstallingPrerequisites => "Installing prerequisites",
            Self::AddingRepository => "Adding PostgreSQL repository",
            Self::UpdatingPackages => "Updating package list",
            Self::InstallingPostgres => "Installing PostgreSQL",
            Self::StartingService => "Starting PostgreSQL service",
            Self::VerifyingInstallation => "Verifying installation",
            Self::Complete => "Installation complete",
        }
    }
}

/// Detect the operating system platform
pub async fn detect_platform(ssh: &mut SshClient) -> Result<Platform> {
    debug!("Detecting platform");

    // Try to read /etc/os-release
    let result = ssh.execute("cat /etc/os-release 2>/dev/null || cat /etc/lsb-release 2>/dev/null");

    if let Ok(output) = result {
        let content = output.stdout.to_lowercase();

        if content.contains("ubuntu") {
            return Ok(Platform::Ubuntu);
        } else if content.contains("debian") {
            return Ok(Platform::Debian);
        } else if content.contains("rhel") || content.contains("red hat") {
            return Ok(Platform::RedHat);
        } else if content.contains("centos") {
            return Ok(Platform::CentOS);
        } else if content.contains("alpine") {
            return Ok(Platform::Alpine);
        }
    }

    // Fallback: check for specific files
    if ssh.execute("test -f /etc/debian_version").is_ok() {
        return Ok(Platform::Debian);
    }

    warn!("Could not detect platform, using Unknown");
    Ok(Platform::Unknown("Unknown".to_string()))
}

/// Get system information
pub async fn get_system_info(ssh: &mut SshClient) -> Result<SystemInfo> {
    debug!("Gathering system information");

    let platform = detect_platform(ssh).await?;

    // Get OS version
    let os_version = ssh
        .execute("lsb_release -d 2>/dev/null | cut -f2 || cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '\"'")
        .map(|o| o.stdout.trim().to_string())
        .unwrap_or_else(|_| "Unknown".to_string());

    // Get total RAM in MB
    let total_ram_mb = ssh
        .execute("free -m | grep Mem: | awk '{print $2}'")
        .ok()
        .and_then(|o| o.stdout.trim().parse().ok())
        .unwrap_or(0);

    // Get free disk space in MB (root partition)
    let free_disk_mb = ssh
        .execute("df -m / | tail -1 | awk '{print $4}'")
        .ok()
        .and_then(|o| o.stdout.trim().parse().ok())
        .unwrap_or(0);

    // Get CPU cores
    let cpu_cores = ssh
        .execute("nproc")
        .ok()
        .and_then(|o| o.stdout.trim().parse().ok())
        .unwrap_or(1);

    Ok(SystemInfo {
        platform,
        os_version,
        total_ram_mb,
        free_disk_mb,
        cpu_cores,
    })
}

/// Check system requirements
pub async fn check_requirements(
    ssh: &mut SshClient,
    requirements: &SystemRequirements,
) -> Result<SystemInfo> {
    info!("Checking system requirements");

    let sys_info = get_system_info(ssh).await?;

    debug!("System info: {:?}", sys_info);

    // Check platform support
    if !sys_info.platform.is_supported() {
        return Err(Error::Installation(format!(
            "Unsupported platform: {}. Currently only Debian and Ubuntu are supported.",
            sys_info.platform.as_str()
        )));
    }

    // Check RAM
    if sys_info.total_ram_mb < requirements.min_ram_mb {
        return Err(Error::Installation(format!(
            "Insufficient RAM: {}MB available, {}MB required",
            sys_info.total_ram_mb, requirements.min_ram_mb
        )));
    }

    // Check disk space
    if sys_info.free_disk_mb < requirements.min_disk_mb {
        return Err(Error::Installation(format!(
            "Insufficient disk space: {}MB available, {}MB required",
            sys_info.free_disk_mb, requirements.min_disk_mb
        )));
    }

    // Check CPU cores
    if sys_info.cpu_cores < requirements.min_cpu_cores {
        warn!(
            "Low CPU cores: {} available, {} recommended",
            sys_info.cpu_cores, requirements.min_cpu_cores
        );
    }

    info!("✓ System requirements check passed");
    Ok(sys_info)
}

/// Check if PostgreSQL version is available
pub async fn check_version_available(ssh: &mut SshClient, version: &str) -> Result<bool> {
    debug!("Checking if PostgreSQL {} is available", version);

    // First ensure repository is added
    let has_repo = ssh
        .execute("test -f /etc/apt/sources.list.d/pgdg.list")
        .is_ok();

    if !has_repo {
        // Add repository temporarily to check
        ssh.execute("DEBIAN_FRONTEND=noninteractive apt-get update -y")
            .ok();
        ssh.execute("DEBIAN_FRONTEND=noninteractive apt-get install -y gnupg2 wget lsb-release")
            .ok();
        ssh.execute(
            "wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -",
        )
        .ok();
        ssh.execute(r#"echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list"#).ok();
        ssh.execute("apt-get update -y").ok();
    }

    // Check if package is available
    let package_name = format!("postgresql-{}", version);
    let result = ssh.execute(&format!("apt-cache show {} 2>&1", package_name));

    match result {
        Ok(output) => {
            let available = output
                .stdout
                .contains(&format!("Package: {}", package_name));
            debug!("PostgreSQL {} available: {}", version, available);
            Ok(available)
        }
        Err(_) => {
            debug!("PostgreSQL {} not found in repositories", version);
            Ok(false)
        }
    }
}

/// Upgrade PostgreSQL to a new version
pub async fn upgrade(
    ssh: &mut SshClient,
    from_version: &str,
    to_version: &str,
    _config: &PostgresConfig,
) -> Result<()> {
    info!(
        "Upgrading PostgreSQL from {} to {}",
        from_version, to_version
    );

    // Check if target version is available
    if !check_version_available(ssh, to_version).await? {
        return Err(Error::Installation(format!(
            "PostgreSQL version {} is not available",
            to_version
        )));
    }

    // Stop current service
    info!("Stopping PostgreSQL {}", from_version);
    ssh.execute("systemctl stop postgresql")
        .map_err(|e| Error::ServiceError(format!("Failed to stop service: {}", e)))?;

    // Install new version
    info!("Installing PostgreSQL {}", to_version);
    let install_cmd = format!(
        "DEBIAN_FRONTEND=noninteractive apt-get install -y postgresql-{}",
        to_version
    );
    ssh.execute(&install_cmd)
        .map_err(|e| Error::Installation(format!("Failed to install new version: {}", e)))?;

    // Run pg_upgrade (simplified - in production this needs more careful handling)
    info!("Note: Manual data migration may be required");
    warn!(
        "PostgreSQL {} installed. You may need to migrate data from version {}",
        to_version, from_version
    );

    // Start new service
    info!("Starting PostgreSQL {}", to_version);
    ssh.execute("systemctl start postgresql")
        .map_err(|e| Error::ServiceError(format!("Failed to start service: {}", e)))?;

    info!("Upgrade to PostgreSQL {} completed", to_version);
    info!("⚠️  Please verify your data and configuration");

    Ok(())
}

/// Verify installation comprehensively
pub async fn verify_installation(ssh: &mut SshClient, config: &PostgresConfig) -> Result<()> {
    info!("Performing comprehensive installation verification");

    // 1. Check PostgreSQL is installed
    let psql_check = ssh.execute("which psql");
    if psql_check.is_err() {
        return Err(Error::Installation(
            "PostgreSQL not found in PATH".to_string(),
        ));
    }

    // 2. Check version
    let version_output = ssh
        .execute("psql --version")
        .map_err(|e| Error::Installation(format!("Failed to get version: {}", e)))?;

    if !version_output.stdout.contains(&config.version) {
        return Err(Error::Installation(format!(
            "Version mismatch: expected {}, got {}",
            config.version, version_output.stdout
        )));
    }
    info!("✓ PostgreSQL version {} confirmed", config.version);

    // 3. Check service is running
    let service_status = ssh
        .execute("systemctl is-active postgresql")
        .map_err(|e| Error::ServiceError(format!("Failed to check service: {}", e)))?;

    if service_status.stdout.trim() != "active" {
        return Err(Error::ServiceError("Service is not active".to_string()));
    }
    info!("✓ PostgreSQL service is running");

    // 4. Check service is enabled
    let service_enabled = ssh
        .execute("systemctl is-enabled postgresql")
        .map_err(|e| Error::ServiceError(format!("Failed to check if enabled: {}", e)))?;

    if service_enabled.stdout.trim() != "enabled" {
        warn!("PostgreSQL service is not enabled for auto-start");
    } else {
        info!("✓ PostgreSQL service is enabled");
    }

    // 5. Check PostgreSQL is listening
    let listening = ssh
        .execute("ss -tlnp | grep postgres || netstat -tlnp | grep postgres")
        .is_ok();

    if !listening {
        warn!("PostgreSQL may not be listening on expected port");
    } else {
        info!("✓ PostgreSQL is listening for connections");
    }

    // 6. Check configuration files exist
    let config_dir = config.config_dir();
    let conf_exists = ssh
        .execute(&format!("test -f {}/postgresql.conf", config_dir))
        .is_ok();

    if !conf_exists {
        return Err(Error::Configuration(format!(
            "Configuration file not found: {}/postgresql.conf",
            config_dir
        )));
    }
    info!("✓ Configuration files exist");

    // 7. Check data directory
    let data_dir_check = ssh
        .execute(&format!("test -d /var/lib/postgresql/{}", config.version))
        .is_ok();

    if !data_dir_check {
        warn!("Data directory may not exist");
    } else {
        info!("✓ Data directory exists");
    }

    info!("✅ Installation verification complete");
    Ok(())
}

/// Rollback installation on failure
pub async fn rollback_installation(ssh: &mut SshClient, version: &str) -> Result<()> {
    warn!("Rolling back PostgreSQL {} installation", version);

    // Stop service if running
    let _ = ssh.execute("systemctl stop postgresql");

    // Remove package
    let remove_cmd = format!("apt-get remove -y postgresql-{}", version);
    ssh.execute(&remove_cmd)
        .map_err(|e| Error::Uninstallation(format!("Rollback failed: {}", e)))?;

    // Clean up
    let _ = ssh.execute("apt-get autoremove -y");

    info!("Rollback completed");
    Ok(())
}

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

    #[test]
    fn test_platform_detection() {
        let platform = Platform::Ubuntu;
        assert_eq!(platform.as_str(), "Ubuntu");
        assert!(platform.is_supported());

        let unknown = Platform::Unknown("Custom".to_string());
        assert_eq!(unknown.as_str(), "Custom");
        assert!(!unknown.is_supported());
    }

    #[test]
    fn test_system_requirements_default() {
        let req = SystemRequirements::default();
        assert_eq!(req.min_ram_mb, 1024);
        assert_eq!(req.min_disk_mb, 5120);
        assert_eq!(req.min_cpu_cores, 1);
    }

    #[test]
    fn test_installation_step_description() {
        let step = InstallationStep::CheckingRequirements;
        assert_eq!(step.description(), "Checking system requirements");

        let step = InstallationStep::InstallingPostgres;
        assert_eq!(step.description(), "Installing PostgreSQL");
    }
}
