//! Backup and recovery procedures for the persistence layer
//!
//! This module provides comprehensive backup and recovery capabilities
//! for all database systems in the trading platform.

use serde::{Deserialize, Serialize};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
use tokio::fs;
use tokio::process::Command as AsyncCommand;

use super::PersistenceConfig;

/// Backup-specific errors
#[derive(Debug, Error)]
/// BackupError
///
/// Auto-generated documentation placeholder - enhance with specifics
pub enum BackupError {
    #[error("IO error: {0}")]
    // Io variant
    Io(#[from] std::io::Error),
    #[error("Command execution failed: {0}")]
    // CommandFailed variant
    CommandFailed(String),
    #[error("Configuration error: {0}")]
    // Configuration variant
    Configuration(String),
    #[error("Backup validation failed: {0}")]
    // ValidationFailed variant
    ValidationFailed(String),
    #[error("Recovery failed: {0}")]
    // RecoveryFailed variant
    RecoveryFailed(String),
}

/// Backup configuration and settings
#[derive(Debug, Clone, Deserialize, Serialize)]
/// BackupConfig
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct BackupConfig {
    /// Base directory for backup storage
    pub backup_directory: String,
    /// Whether to compress backups
    pub enable_compression: bool,
    /// Whether to encrypt backups
    pub enable_encryption: bool,
    /// Encryption key (should be from environment or secure storage)
    pub encryption_key: Option<String>,
    /// Maximum backup retention period in days
    pub retention_days: u32,
    /// Whether to verify backup integrity after creation
    pub verify_backups: bool,
    /// Whether to include time-series data in backups
    pub include_timeseries: bool,
    /// Whether to include analytics data in backups
    pub include_analytics: bool,
}

impl Default for BackupConfig {
    fn default() -> Self {
        Self {
            backup_directory: "/var/backups/foxhunt".to_owned(),
            enable_compression: true,
            enable_encryption: true,
            encryption_key: None,
            retention_days: 30,
            verify_backups: true,
            include_timeseries: false, // Usually too large
            include_analytics: false,  // Usually too large
        }
    }
}

/// Backup metadata and information
#[derive(Debug, Clone, Serialize, Deserialize)]
/// BackupInfo
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct BackupInfo {
    /// Backup Id
    pub backup_id: String,
    /// Timestamp
    pub timestamp: u64,
    /// Components
    pub components: Vec<BackupComponent>,
    /// Total Size Bytes
    pub total_size_bytes: u64,
    /// Compression Enabled
    pub compression_enabled: bool,
    /// Encryption Enabled
    pub encryption_enabled: bool,
    /// Verification Status
    pub verification_status: BackupVerificationStatus,
    /// Backup Path
    pub backup_path: String,
}

/// Individual component backup information
#[derive(Debug, Clone, Serialize, Deserialize)]
/// BackupComponent
///
/// Auto-generated documentation placeholder - enhance with specifics
pub struct BackupComponent {
    /// Component Type
    pub component_type: ComponentType,
    /// File Path
    pub file_path: String,
    /// Size Bytes
    pub size_bytes: u64,
    /// Checksum
    pub checksum: String,
    /// Compression Ratio
    pub compression_ratio: Option<f64>,
}

/// Types of components that can be backed up
#[derive(Debug, Clone, Serialize, Deserialize)]
/// ComponentType
///
/// Auto-generated documentation placeholder - enhance with specifics
pub enum ComponentType {
    // PostgresqlDump variant
    PostgresqlDump,
    // InfluxdbExport variant
    InfluxdbExport,
    // RedisSnapshot variant
    RedisSnapshot,
    // ClickhouseBackup variant
    ClickhouseBackup,
    // ConfigurationFiles variant
    ConfigurationFiles,
    // MigrationScripts variant
    MigrationScripts,
}

/// Backup verification status
#[derive(Debug, Clone, Serialize, Deserialize)]
/// BackupVerificationStatus
///
/// Auto-generated documentation placeholder - enhance with specifics
pub enum BackupVerificationStatus {
    // NotVerified variant
    NotVerified,
    // Verified variant
    Verified,
    // VerificationFailed variant
    VerificationFailed(String),
}

/// Main backup manager
#[derive(Debug)]
pub struct BackupManager {
    config: BackupConfig,
    persistence_config: PersistenceConfig,
}

impl BackupManager {
    /// Create a new backup manager
    pub const fn new(config: BackupConfig, persistence_config: PersistenceConfig) -> Self {
        Self {
            config,
            persistence_config,
        }
    }

    /// Create a full backup of all systems
    pub async fn create_full_backup(&self) -> Result<BackupInfo, BackupError> {
        let backup_id = self.generate_backup_id();
        let backup_timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_err(|e| {
                BackupError::Io(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    format!("Failed to get system time: {}", e),
                ))
            })?
            .as_secs();

        // Create backup directory
        let backup_dir = Path::new(&self.config.backup_directory).join(&backup_id);
        fs::create_dir_all(&backup_dir).await?;

        let mut components = Vec::new();
        let mut total_size = 0_u64;

        // Backup PostgreSQL
        if let Ok(pg_backup) = self.backup_postgresql(&backup_dir).await {
            total_size += pg_backup.size_bytes;
            components.push(pg_backup);
        }

        // Backup InfluxDB (if configured)
        if self.config.include_timeseries {
            if let Ok(influx_backup) = self.backup_influxdb(&backup_dir).await {
                total_size += influx_backup.size_bytes;
                components.push(influx_backup);
            }
        }

        // Backup Redis
        if let Ok(redis_backup) = self.backup_redis(&backup_dir).await {
            total_size += redis_backup.size_bytes;
            components.push(redis_backup);
        }

        // Backup ClickHouse (if configured)
        if self.config.include_analytics {
            if let Ok(ch_backup) = self.backup_clickhouse(&backup_dir).await {
                total_size += ch_backup.size_bytes;
                components.push(ch_backup);
            }
        }

        // Backup configuration files
        if let Ok(config_backup) = self.backup_configuration(&backup_dir).await {
            total_size += config_backup.size_bytes;
            components.push(config_backup);
        }

        // Create backup metadata
        let mut backup_info = BackupInfo {
            backup_id: backup_id.clone(),
            timestamp: backup_timestamp,
            components,
            total_size_bytes: total_size,
            compression_enabled: self.config.enable_compression,
            encryption_enabled: self.config.enable_encryption,
            verification_status: BackupVerificationStatus::NotVerified,
            backup_path: backup_dir.to_string_lossy().to_string(),
        };

        // Verify backup if enabled
        if self.config.verify_backups {
            backup_info.verification_status = self.verify_backup(&backup_info).await;
        }

        // Save backup metadata
        self.save_backup_metadata(&backup_info).await?;

        // Clean up old backups
        self.cleanup_old_backups().await?;

        // Ok variant
        Ok(backup_info)
    }

    /// Backup `PostgreSQL` database
    async fn backup_postgresql(&self, backup_dir: &Path) -> Result<BackupComponent, BackupError> {
        let backup_file = backup_dir.join("postgresql_dump.sql");

        // Extract connection details from URL
        let url = &self.persistence_config.postgres.url;

        // Use pg_dump to create backup
        let mut cmd = AsyncCommand::new("pg_dump");
        cmd.arg(url)
            .arg("--verbose")
            .arg("--no-password")
            .arg("--format=custom")
            .arg("--file")
            .arg(&backup_file);

        let output = cmd.output().await?;

        if !output.status.success() {
            return Err(BackupError::CommandFailed(format!(
                "pg_dump failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        let metadata = fs::metadata(&backup_file).await?;
        let checksum = self.calculate_file_checksum(&backup_file).await?;

        Ok(BackupComponent {
            component_type: ComponentType::PostgresqlDump,
            file_path: backup_file.to_string_lossy().to_string(),
            size_bytes: metadata.len(),
            checksum,
            compression_ratio: None,
        })
    }

    /// Backup `InfluxDB` data
    async fn backup_influxdb(&self, backup_dir: &Path) -> Result<BackupComponent, BackupError> {
        let backup_file = backup_dir.join("influxdb_export.tar.gz");

        // Use influxd backup command
        let mut cmd = AsyncCommand::new("influxd");
        cmd.arg("backup")
            .arg("--host")
            .arg(&format!(
                "{}:8088",
                self.extract_host_from_url(&self.persistence_config.influx.url)
            ))
            .arg(&backup_file);

        let output = cmd.output().await?;

        if !output.status.success() {
            return Err(BackupError::CommandFailed(format!(
                "influxd backup failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        let metadata = fs::metadata(&backup_file).await?;
        let checksum = self.calculate_file_checksum(&backup_file).await?;

        Ok(BackupComponent {
            component_type: ComponentType::InfluxdbExport,
            file_path: backup_file.to_string_lossy().to_string(),
            size_bytes: metadata.len(),
            checksum,
            compression_ratio: None,
        })
    }

    /// Backup `Redis` data
    async fn backup_redis(&self, backup_dir: &Path) -> Result<BackupComponent, BackupError> {
        let backup_file = backup_dir.join("redis_dump.rdb");

        // Use redis-cli to create backup
        let mut cmd = AsyncCommand::new("redis-cli");
        cmd.arg("--rdb").arg(&backup_file);

        let output = cmd.output().await?;

        if !output.status.success() {
            return Err(BackupError::CommandFailed(format!(
                "redis-cli backup failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        let metadata = fs::metadata(&backup_file).await?;
        let checksum = self.calculate_file_checksum(&backup_file).await?;

        Ok(BackupComponent {
            component_type: ComponentType::RedisSnapshot,
            file_path: backup_file.to_string_lossy().to_string(),
            size_bytes: metadata.len(),
            checksum,
            compression_ratio: None,
        })
    }

    /// Backup `ClickHouse` data
    async fn backup_clickhouse(&self, backup_dir: &Path) -> Result<BackupComponent, BackupError> {
        let backup_file = backup_dir.join("clickhouse_backup.tar.gz");

        // Use clickhouse-backup tool if available
        let mut cmd = AsyncCommand::new("clickhouse-backup");
        cmd.arg("create").arg("--table=*.*").arg(&backup_file);

        let output = cmd.output().await?;

        if !output.status.success() {
            return Err(BackupError::CommandFailed(format!(
                "clickhouse-backup failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        let metadata = fs::metadata(&backup_file).await?;
        let checksum = self.calculate_file_checksum(&backup_file).await?;

        Ok(BackupComponent {
            component_type: ComponentType::ClickhouseBackup,
            file_path: backup_file.to_string_lossy().to_string(),
            size_bytes: metadata.len(),
            checksum,
            compression_ratio: None,
        })
    }

    /// Backup configuration files
    async fn backup_configuration(
        &self,
        backup_dir: &Path,
    ) -> Result<BackupComponent, BackupError> {
        let backup_file = backup_dir.join("configuration.tar.gz");

        // Create tar archive of configuration directories
        let mut cmd = AsyncCommand::new("tar");
        cmd.arg("czf")
            .arg(&backup_file)
            .arg("/home/jgrusewski/Work/foxhunt/config")
            .arg("/home/jgrusewski/Work/foxhunt/migrations")
            .arg("/home/jgrusewski/Work/foxhunt/certs");

        let output = cmd.output().await?;

        if !output.status.success() {
            return Err(BackupError::CommandFailed(format!(
                "Configuration backup failed: {}",
                String::from_utf8_lossy(&output.stderr)
            )));
        }

        let metadata = fs::metadata(&backup_file).await?;
        let checksum = self.calculate_file_checksum(&backup_file).await?;

        Ok(BackupComponent {
            component_type: ComponentType::ConfigurationFiles,
            file_path: backup_file.to_string_lossy().to_string(),
            size_bytes: metadata.len(),
            checksum,
            compression_ratio: None,
        })
    }

    /// Verify backup integrity
    async fn verify_backup(&self, backup_info: &BackupInfo) -> BackupVerificationStatus {
        for component in &backup_info.components {
            match self.verify_component(component).await {
                Ok(()) => continue,
                Err(e) => return BackupVerificationStatus::VerificationFailed(e.to_string()),
            }
        }

        BackupVerificationStatus::Verified
    }

    /// Verify individual backup component
    async fn verify_component(&self, component: &BackupComponent) -> Result<(), BackupError> {
        let file_path = Path::new(&component.file_path);

        // Check if file exists
        if !file_path.exists() {
            return Err(BackupError::ValidationFailed(format!(
                "Backup file not found: {}",
                component.file_path
            )));
        }

        // Verify file size
        let metadata = fs::metadata(file_path).await?;
        if metadata.len() != component.size_bytes {
            return Err(BackupError::ValidationFailed(format!(
                "File size mismatch for {}: expected {}, got {}",
                component.file_path,
                component.size_bytes,
                metadata.len()
            )));
        }

        // Verify checksum
        let actual_checksum = self.calculate_file_checksum(file_path).await?;
        if actual_checksum != component.checksum {
            return Err(BackupError::ValidationFailed(format!(
                "Checksum mismatch for {}: expected {}, got {}",
                component.file_path, component.checksum, actual_checksum
            )));
        }

        Ok(())
    }

    /// Save backup metadata to a JSON file
    async fn save_backup_metadata(&self, backup_info: &BackupInfo) -> Result<(), BackupError> {
        let metadata_file = Path::new(&backup_info.backup_path).join("backup_metadata.json");
        let json_content = serde_json::to_string_pretty(backup_info)
            .map_err(|e| BackupError::Configuration(format!("JSON serialization failed: {}", e)))?;

        fs::write(metadata_file, json_content).await?;
        Ok(())
    }

    /// Clean up old backups based on retention policy
    async fn cleanup_old_backups(&self) -> Result<(), BackupError> {
        let backup_dir = Path::new(&self.config.backup_directory);
        let retention_seconds = self.config.retention_days as u64 * 24 * 3600;
        let current_time = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_err(|e| {
                BackupError::Io(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    format!("Failed to get system time: {}", e),
                ))
            })?
            .as_secs();
        let cutoff_time = current_time.saturating_sub(retention_seconds);

        let mut entries = fs::read_dir(backup_dir).await?;

        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            if path.is_dir() {
                // Check backup metadata to get timestamp
                let metadata_file = path.join("backup_metadata.json");
                if metadata_file.exists() {
                    let content = fs::read_to_string(metadata_file).await?;
                    if let Ok(backup_info) = serde_json::from_str::<BackupInfo>(&content) {
                        if backup_info.timestamp < cutoff_time {
                            println!("Removing old backup: {}", backup_info.backup_id);
                            fs::remove_dir_all(&path).await?;
                        }
                    }
                }
            }
        }

        Ok(())
    }

    /// Generate a unique backup ID
    fn generate_backup_id(&self) -> String {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or_else(|_| {
                // Fallback to a random ID if system time is unavailable
                use std::hash::{Hash, Hasher};
                let mut hasher = std::collections::hash_map::DefaultHasher::new();
                std::process::id().hash(&mut hasher);
                std::thread::current().id().hash(&mut hasher);
                hasher.finish()
            });

        format!("backup_{}", timestamp)
    }

    /// Calculate SHA-256 checksum of a file
    async fn calculate_file_checksum(&self, file_path: &Path) -> Result<String, BackupError> {
        use sha2::{Digest, Sha256};

        let content = fs::read(file_path).await?;
        let mut hasher = Sha256::new();
        hasher.update(&content);
        Ok(format!("{:x}", hasher.finalize()))
    }

    /// Extract host from URL
    fn extract_host_from_url(&self, url: &str) -> String {
        if let Ok(parsed) = url::Url::parse(url) {
            parsed.host_str().unwrap_or("localhost").to_owned()
        } else {
            "localhost".to_owned()
        }
    }
}

/// Convenience function to create a full backup
pub async fn create_full_backup(config: &PersistenceConfig) -> Result<BackupInfo, BackupError> {
    let backup_config = BackupConfig::default();
    let manager = BackupManager::new(backup_config, config.clone());
    manager.create_full_backup().await
}
