use anyhow::Result;
use uuid::Uuid;

/// Core wipe orchestration engine.
///
/// Coordinates the full wipe lifecycle: pre-wipe steps (backups, warnings,
/// player kicks), the wipe itself (map swap, plugin data cleanup, seed
/// rotation), and post-wipe verification (health checks, rollback on
/// failure). Works through the PanelAdapter trait so it is panel-agnostic.
///
/// All wipe executions are logged to the wipe_history table with full
/// execution traces for audit and debugging.
pub struct WipeEngine {
    pub db: sqlx::PgPool,
    pub nats: async_nats::Client,
}

impl WipeEngine {
    pub fn new(db: sqlx::PgPool, nats: async_nats::Client) -> Self {
        Self { db, nats }
    }

    /// Execute a full wipe sequence for the given wipe history record.
    ///
    /// Orchestrates: pre-wipe -> wipe actions -> post-wipe verification.
    /// On failure at any stage, triggers rollback if configured.
    pub async fn execute_wipe(&self, wipe_history_id: Uuid) -> Result<()> {
        use anyhow::Context;

        // Load wipe history record with profile data
        let wipe_record: Option<(Uuid, Uuid, String, String)> = sqlx::query_as(
            "SELECT wh.license_id, wh.wipe_profile_id, wh.wipe_type, sc.server_name
             FROM wipe_history wh
             JOIN wipe_profiles wp ON wp.id = wh.wipe_profile_id
             LEFT JOIN server_config sc ON sc.license_id = wh.license_id
             WHERE wh.id = $1",
        )
        .bind(wipe_history_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load wipe history")?;

        let (license_id, wipe_profile_id, wipe_type, _server_name) = wipe_record
            .context("Wipe history record not found")?;

        tracing::info!(
            "Starting wipe execution: {} (type: {}, license: {})",
            wipe_history_id,
            wipe_type,
            license_id
        );

        // Update status to pre_wipe and set started_at timestamp
        sqlx::query(
            "UPDATE wipe_history
             SET status = 'pre_wipe', started_at = NOW(),
                 execution_log = execution_log || $1::jsonb
             WHERE id = $2",
        )
        .bind(serde_json::json!([{
            "timestamp": chrono::Utc::now().to_rfc3339(),
            "phase": "init",
            "message": "Wipe execution started"
        }]))
        .bind(wipe_history_id)
        .execute(&self.db)
        .await
        .context("Failed to update wipe status to pre_wipe")?;

        // Publish NATS event for status change
        let subject = format!("corrosion.wipes.{}.status", license_id);
        let event_payload = serde_json::json!({
            "wipe_history_id": wipe_history_id,
            "license_id": license_id,
            "status": "pre_wipe",
            "wipe_type": wipe_type,
            "timestamp": chrono::Utc::now().to_rfc3339()
        });
        if let Err(e) = self.nats.publish(subject.clone(), event_payload.to_string().into()).await {
            tracing::warn!("Failed to publish NATS event: {}", e);
        }

        // Execute pre-wipe phase
        if let Err(e) = self.execute_pre_wipe(wipe_history_id).await {
            tracing::error!("Pre-wipe phase failed: {}", e);
            self.mark_wipe_failed(wipe_history_id, &format!("Pre-wipe failed: {}", e))
                .await?;
            self.publish_status_event(license_id, wipe_history_id, "failed", &wipe_type)
                .await;
            return Err(e);
        }

        // Update status to wiping
        sqlx::query(
            "UPDATE wipe_history
             SET status = 'wiping',
                 execution_log = execution_log || $1::jsonb
             WHERE id = $2",
        )
        .bind(serde_json::json!([{
            "timestamp": chrono::Utc::now().to_rfc3339(),
            "phase": "wipe",
            "message": "Executing wipe actions"
        }]))
        .bind(wipe_history_id)
        .execute(&self.db)
        .await
        .context("Failed to update status to wiping")?;

        self.publish_status_event(license_id, wipe_history_id, "wiping", &wipe_type)
            .await;

        // Execute wipe actions
        if let Err(e) = self.execute_wipe_actions(wipe_history_id).await {
            tracing::error!("Wipe actions failed: {}", e);
            self.mark_wipe_failed(wipe_history_id, &format!("Wipe actions failed: {}", e))
                .await?;
            self.publish_status_event(license_id, wipe_history_id, "failed", &wipe_type)
                .await;

            // Check if rollback is configured
            let rollback_enabled: Option<(bool,)> = sqlx::query_as(
                "SELECT (post_wipe_config->>'rollback_on_failure')::boolean
                 FROM wipe_profiles WHERE id = $1",
            )
            .bind(wipe_profile_id)
            .fetch_optional(&self.db)
            .await?;

            if rollback_enabled.map(|(r,)| r).unwrap_or(false) {
                tracing::warn!("Triggering rollback due to wipe failure");
                let _ = self.execute_rollback(wipe_history_id).await;
            }

            return Err(e);
        }

        // Update status to post_wipe
        sqlx::query(
            "UPDATE wipe_history
             SET status = 'post_wipe',
                 execution_log = execution_log || $1::jsonb
             WHERE id = $2",
        )
        .bind(serde_json::json!([{
            "timestamp": chrono::Utc::now().to_rfc3339(),
            "phase": "post_wipe",
            "message": "Running post-wipe verification"
        }]))
        .bind(wipe_history_id)
        .execute(&self.db)
        .await
        .context("Failed to update status to post_wipe")?;

        self.publish_status_event(license_id, wipe_history_id, "post_wipe", &wipe_type)
            .await;

        // Execute post-wipe verification
        if let Err(e) = self.execute_post_wipe_verification(wipe_history_id).await {
            tracing::error!("Post-wipe verification failed: {}", e);
            self.mark_wipe_failed(wipe_history_id, &format!("Post-wipe verification failed: {}", e))
                .await?;
            self.publish_status_event(license_id, wipe_history_id, "failed", &wipe_type)
                .await;

            // Check if rollback is configured
            let rollback_enabled: Option<(bool,)> = sqlx::query_as(
                "SELECT (post_wipe_config->>'rollback_on_failure')::boolean
                 FROM wipe_profiles WHERE id = $1",
            )
            .bind(wipe_profile_id)
            .fetch_optional(&self.db)
            .await?;

            if rollback_enabled.map(|(r,)| r).unwrap_or(false) {
                tracing::warn!("Triggering rollback due to verification failure");
                let _ = self.execute_rollback(wipe_history_id).await;
            }

            return Err(e);
        }

        // Mark wipe as successful
        sqlx::query(
            "UPDATE wipe_history
             SET status = 'success', completed_at = NOW(),
                 execution_log = execution_log || $1::jsonb
             WHERE id = $2",
        )
        .bind(serde_json::json!([{
            "timestamp": chrono::Utc::now().to_rfc3339(),
            "phase": "complete",
            "message": "Wipe completed successfully"
        }]))
        .bind(wipe_history_id)
        .execute(&self.db)
        .await
        .context("Failed to mark wipe as successful")?;

        self.publish_status_event(license_id, wipe_history_id, "success", &wipe_type)
            .await;

        tracing::info!("Wipe {} completed successfully", wipe_history_id);

        Ok(())
    }

    /// Execute pre-wipe steps: countdown warnings, player kicks, backups,
    /// Discord/Pushbullet announcements, custom commands.
    pub async fn execute_pre_wipe(&self, wipe_history_id: Uuid) -> Result<()> {
        use anyhow::Context;

        // Load wipe history and profile
        let record: Option<(Uuid, Uuid, String, serde_json::Value)> = sqlx::query_as(
            "SELECT wh.license_id, wh.wipe_profile_id, wh.wipe_type, wp.pre_wipe_config
             FROM wipe_history wh
             JOIN wipe_profiles wp ON wp.id = wh.wipe_profile_id
             WHERE wh.id = $1",
        )
        .bind(wipe_history_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load wipe record for pre-wipe")?;

        let (license_id, _wipe_profile_id, wipe_type, pre_wipe_config) = record
            .context("Wipe record not found")?;

        tracing::info!("Executing pre-wipe phase for wipe {}", wipe_history_id);

        // Parse pre-wipe config
        let config = pre_wipe_config;
        let enabled = config.get("enabled").and_then(|v| v.as_bool()).unwrap_or(true);
        if !enabled {
            tracing::info!("Pre-wipe phase disabled, skipping");
            return Ok(());
        }

        let backup_before_wipe = config.get("backup_before_wipe").and_then(|v| v.as_bool()).unwrap_or(true);
        let countdown_warnings = config.get("countdown_warnings")
            .and_then(|v| v.as_array())
            .map(|arr| arr.iter().filter_map(|v| v.as_i64()).collect::<Vec<_>>())
            .unwrap_or_else(|| vec![30, 15, 5, 1]);
        let countdown_unit = config.get("countdown_unit").and_then(|v| v.as_str()).unwrap_or("minutes");
        let kick_players = config.get("kick_players_before_wipe").and_then(|v| v.as_bool()).unwrap_or(true);
        let kick_message = config.get("kick_message").and_then(|v| v.as_str()).unwrap_or("Server is wiping. Be back shortly!");
        let run_final_save = config.get("run_final_save").and_then(|v| v.as_bool()).unwrap_or(true);
        let discord_pre_announce = config.get("discord_pre_announce").and_then(|v| v.as_bool()).unwrap_or(true);
        let pushbullet_notify = config.get("pushbullet_notify").and_then(|v| v.as_bool()).unwrap_or(false);

        // Load server connection details
        let server_conn: Option<(String, String, Option<String>)> = sqlx::query_as(
            "SELECT connection_type, panel_server_identifier, server_name
             FROM server_connections sc
             LEFT JOIN server_config scfg ON scfg.license_id = sc.license_id
             WHERE sc.license_id = $1",
        )
        .bind(license_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load server connection")?;

        let (connection_type, panel_server_id, server_name) = server_conn
            .context("Server connection not configured")?;

        let server_name = server_name.unwrap_or_else(|| "Server".to_string());

        // Resolve PanelAdapter
        let adapter = self.resolve_panel_adapter(license_id, &connection_type).await?;

        // Send Discord pre-announce notification
        if discord_pre_announce {
            if let Ok(notifier) = self.get_discord_notifier(license_id, &server_name).await {
                let eta_minutes = countdown_warnings.first().copied().unwrap_or(10) as u32;
                if let Err(e) = notifier.send_wipe_start(&wipe_type, eta_minutes).await {
                    tracing::warn!("Failed to send Discord pre-wipe notification: {}", e);
                }
            }
        }

        // Send Pushbullet notification
        if pushbullet_notify {
            if let Ok(notifier) = self.get_pushbullet_notifier(license_id).await {
                if let Err(e) = notifier.send_wipe_start(&server_name, &wipe_type).await {
                    tracing::warn!("Failed to send Pushbullet notification: {}", e);
                }
            }
        }

        // Send countdown warnings via RCON
        for interval in countdown_warnings {
            let message = if countdown_unit == "minutes" {
                format!("Server wipe in {} minute(s)! Please log out.", interval)
            } else {
                format!("Server wipe in {} {}! Please log out.", interval, countdown_unit)
            };

            if let Err(e) = adapter.send_command(&panel_server_id, &format!("say {}", message)).await {
                tracing::warn!("Failed to send countdown warning: {}", e);
            }

            // Wait for the interval (convert to seconds if minutes)
            let wait_secs = if countdown_unit == "minutes" {
                (interval as u64) * 60
            } else {
                interval as u64
            };
            tokio::time::sleep(tokio::time::Duration::from_secs(wait_secs)).await;
        }

        // Create backup if enabled
        if backup_before_wipe {
            tracing::info!("Creating pre-wipe backup");
            let backup_manager = super::backup_manager::BackupManager::new(
                self.db.clone(),
                std::env::var("BACKUP_STORAGE_PATH").unwrap_or_else(|_| "/var/lib/corrosion/backups".to_string())
            );

            match backup_manager.create_backup(adapter.as_ref(), &panel_server_id, license_id, wipe_history_id).await {
                Ok(backup_ref) => {
                    sqlx::query(
                        "UPDATE wipe_history SET backup_reference = $1 WHERE id = $2"
                    )
                    .bind(&backup_ref)
                    .bind(wipe_history_id)
                    .execute(&self.db)
                    .await
                    .context("Failed to update backup reference")?;

                    tracing::info!("Backup created: {}", backup_ref);
                }
                Err(e) => {
                    tracing::error!("Backup creation failed: {}", e);
                    return Err(e).context("Pre-wipe backup failed");
                }
            }
        }

        // Run final save command
        if run_final_save {
            if let Err(e) = adapter.send_command(&panel_server_id, "server.save").await {
                tracing::warn!("Failed to run server.save: {}", e);
            }
            tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
        }

        // Kick all players
        if kick_players {
            tracing::info!("Kicking all players");
            if let Err(e) = adapter.send_command(&panel_server_id, &format!("global.kickall \"{}\"", kick_message)).await {
                tracing::warn!("Failed to kick players: {}", e);
            }
            tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
        }

        // Execute custom commands
        if let Some(custom_commands) = config.get("custom_commands_before").and_then(|v| v.as_array()) {
            for cmd in custom_commands {
                if let Some(cmd_str) = cmd.as_str() {
                    tracing::debug!("Running custom pre-wipe command: {}", cmd_str);
                    if let Err(e) = adapter.send_command(&panel_server_id, cmd_str).await {
                        tracing::warn!("Custom command failed: {}", e);
                    }
                }
            }
        }

        tracing::info!("Pre-wipe phase completed for wipe {}", wipe_history_id);
        Ok(())
    }

    /// Execute the actual wipe actions: stop server, delete/swap map,
    /// wipe plugin data, update seed, restart server.
    pub async fn execute_wipe_actions(&self, wipe_history_id: Uuid) -> Result<()> {
        use anyhow::Context;

        // Load wipe history
        let record: Option<(Uuid, String, String)> = sqlx::query_as(
            "SELECT wh.license_id, wh.wipe_type, wh.trigger_type
             FROM wipe_history wh
             WHERE wh.id = $1",
        )
        .bind(wipe_history_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load wipe history for wipe actions")?;

        let (license_id, wipe_type, trigger_type) = record
            .context("Wipe history not found")?;

        tracing::info!("Executing wipe actions: type={}, trigger={}", wipe_type, trigger_type);

        // Load server connection
        let server_conn: Option<(String, String)> = sqlx::query_as(
            "SELECT connection_type, panel_server_identifier
             FROM server_connections
             WHERE license_id = $1",
        )
        .bind(license_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load server connection")?;

        let (connection_type, panel_server_id) = server_conn
            .context("Server connection not configured")?;

        // Resolve panel adapter
        let adapter = self.resolve_panel_adapter(license_id, &connection_type).await?;

        // Stop the server
        tracing::info!("Stopping server for wipe");
        adapter.stop_server(&panel_server_id).await
            .context("Failed to stop server")?;

        // Wait for server to fully stop
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;

        // Delete map/save files based on wipe type
        let files_to_delete = match wipe_type.as_str() {
            "map" => vec![
                "/server/rust/server/procedural/",
            ],
            "blueprint" => vec![
                "/server/rust/server/procedural/player.blueprints.5.db",
            ],
            "full" => vec![
                "/server/rust/server/procedural/",
                "/server/rust/server/player.identities.5.db",
                "/server/rust/server/player.states.5.db",
                "/server/rust/server/sv.files.5.db",
            ],
            _ => vec![],
        };

        for file_path in files_to_delete {
            tracing::debug!("Deleting: {}", file_path);
            if let Err(e) = adapter.delete_file(&panel_server_id, file_path).await {
                tracing::warn!("Failed to delete {}: {}", file_path, e);
            }
        }

        // Wipe plugin data based on flags
        if wipe_type != "blueprint" {
            let plugins_to_wipe: Vec<(String, Option<String>)> = sqlx::query_as(
                "SELECT plugin_name, data_path
                 FROM plugin_registry
                 WHERE license_id = $1
                   AND (
                     (wipe_on_map = true AND $2 = 'map')
                     OR (wipe_on_bp = true AND $2 = 'blueprint')
                     OR (wipe_on_full = true AND $2 = 'full')
                   )
                   AND never_wipe = false",
            )
            .bind(license_id)
            .bind(&wipe_type)
            .fetch_all(&self.db)
            .await
            .context("Failed to query plugins to wipe")?;

            let mut wiped_plugins = Vec::new();
            for (plugin_name, data_path) in plugins_to_wipe {
                if let Some(path) = data_path {
                    tracing::debug!("Wiping plugin data: {} ({})", plugin_name, path);
                    if let Err(e) = adapter.delete_file(&panel_server_id, &path).await {
                        tracing::warn!("Failed to wipe plugin data for {}: {}", plugin_name, e);
                    } else {
                        wiped_plugins.push(plugin_name);
                    }
                }
            }

            // Update wipe history with wiped plugins
            if !wiped_plugins.is_empty() {
                sqlx::query(
                    "UPDATE wipe_history SET plugins_wiped = $1 WHERE id = $2"
                )
                .bind(&wiped_plugins)
                .bind(wipe_history_id)
                .execute(&self.db)
                .await
                .context("Failed to update wiped plugins list")?;
            }
        }

        // Update seed if configured
        let current_seed: Option<(Option<i32>,)> = sqlx::query_as(
            "SELECT current_seed FROM server_config WHERE license_id = $1"
        )
        .bind(license_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to query current seed")?;

        if let Some((Some(_old_seed),)) = current_seed {
            // Generate new seed
            let new_seed: i32 = rand::random::<u32>() as i32;

            sqlx::query(
                "UPDATE server_config SET current_seed = $1 WHERE license_id = $2"
            )
            .bind(new_seed)
            .bind(license_id)
            .execute(&self.db)
            .await
            .context("Failed to update seed")?;

            tracing::info!("Seed rotated to: {}", new_seed);

            // Update server.cfg with new seed
            let cfg_content = format!("server.seed {}\n", new_seed);
            if let Err(e) = adapter.put_file(&panel_server_id, "/server/rust/server.cfg", cfg_content.as_bytes()).await {
                tracing::warn!("Failed to update server.cfg with new seed: {}", e);
            }
        }

        // Check for custom map rotation
        let map_manager = super::map_manager::MapManager::new(
            self.db.clone(),
            std::env::var("MAP_STORAGE_PATH").unwrap_or_else(|_| "/var/lib/corrosion/maps".to_string())
        );

        if let Ok(rotation) = map_manager.get_rotation(license_id).await {
            if !rotation.is_empty() && wipe_type == "map" {
                tracing::info!("Advancing map rotation");
                if let Ok(next_map_id) = map_manager.advance_rotation(license_id).await {
                    // Download map data and upload to server
                    if let Ok(map_data) = map_manager.get_map_data(next_map_id).await {
                        tracing::info!("Uploading custom map: {}", next_map_id);
                        if let Err(e) = adapter.put_file(&panel_server_id, "/server/rust/server/procedural/CustomMap.map", &map_data).await {
                            tracing::error!("Failed to upload custom map: {}", e);
                        } else {
                            sqlx::query(
                                "UPDATE wipe_history SET map_used = $1 WHERE id = $2"
                            )
                            .bind(next_map_id.to_string())
                            .bind(wipe_history_id)
                            .execute(&self.db)
                            .await
                            .context("Failed to update map_used")?;
                        }
                    }
                }
            }
        }

        // Trigger Steam update if force wipe
        if trigger_type == "force_wipe" {
            tracing::info!("Triggering Steam update for force wipe");
            if let Err(e) = adapter.trigger_steam_update(&panel_server_id).await {
                tracing::warn!("Steam update failed: {}", e);
            }
            tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;
        }

        // Start the server
        tracing::info!("Starting server after wipe");
        adapter.start_server(&panel_server_id).await
            .context("Failed to start server after wipe")?;

        tracing::info!("Wipe actions completed for wipe {}", wipe_history_id);
        Ok(())
    }

    /// Execute post-wipe verification: health checks on server startup,
    /// map correctness, plugin loading, player slot availability.
    pub async fn execute_post_wipe_verification(&self, wipe_history_id: Uuid) -> Result<()> {
        use anyhow::Context;

        // Load wipe record and post-wipe config
        let record: Option<(Uuid, String, serde_json::Value, Option<String>)> = sqlx::query_as(
            "SELECT wh.license_id, wh.wipe_type, wp.post_wipe_config, sc.server_name
             FROM wipe_history wh
             JOIN wipe_profiles wp ON wp.id = wh.wipe_profile_id
             LEFT JOIN server_config sc ON sc.license_id = wh.license_id
             WHERE wh.id = $1",
        )
        .bind(wipe_history_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load wipe record for post-wipe verification")?;

        let (license_id, wipe_type, post_wipe_config, server_name) = record
            .context("Wipe record not found")?;

        let server_name = server_name.unwrap_or_else(|| "Server".to_string());

        tracing::info!("Executing post-wipe verification for wipe {}", wipe_history_id);

        // Parse post-wipe config
        let config = post_wipe_config;
        let enabled = config.get("enabled").and_then(|v| v.as_bool()).unwrap_or(true);
        if !enabled {
            tracing::info!("Post-wipe verification disabled, skipping");
            return Ok(());
        }

        let verify_server_started = config.get("verify_server_started").and_then(|v| v.as_bool()).unwrap_or(true);
        let _verify_correct_map = config.get("verify_correct_map").and_then(|v| v.as_bool()).unwrap_or(true);
        let _verify_plugins_loaded = config.get("verify_plugins_loaded").and_then(|v| v.as_bool()).unwrap_or(true);
        let _verify_player_slots_open = config.get("verify_player_slots_open").and_then(|v| v.as_bool()).unwrap_or(true);
        let max_restart_attempts = config.get("max_restart_attempts").and_then(|v| v.as_i64()).unwrap_or(3) as u32;
        let discord_post_announce = config.get("discord_post_announce").and_then(|v| v.as_bool()).unwrap_or(true);
        let pushbullet_notify = config.get("pushbullet_notify").and_then(|v| v.as_bool()).unwrap_or(false);

        // Load server connection
        let server_conn: Option<(String, String)> = sqlx::query_as(
            "SELECT connection_type, panel_server_identifier
             FROM server_connections
             WHERE license_id = $1",
        )
        .bind(license_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load server connection")?;

        let (connection_type, panel_server_id) = server_conn
            .context("Server connection not configured")?;

        // Resolve panel adapter
        let adapter = self.resolve_panel_adapter(license_id, &connection_type).await?;

        // Run health checks with retries
        let health_checker = super::health_checker::HealthChecker::new(self.db.clone());

        let mut health_passed = false;
        for attempt in 1..=max_restart_attempts {
            tracing::info!("Health check attempt {}/{}", attempt, max_restart_attempts);

            // Wait for server to start
            if verify_server_started {
                tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;
            }

            // Run health verification
            match health_checker.verify_server_health(adapter.as_ref(), &panel_server_id, license_id).await {
                Ok(true) => {
                    health_passed = true;
                    tracing::info!("Health checks passed on attempt {}", attempt);
                    break;
                }
                Ok(false) => {
                    tracing::warn!("Health checks failed on attempt {}", attempt);
                }
                Err(e) => {
                    tracing::error!("Health check error on attempt {}: {}", attempt, e);
                }
            }

            // Restart server if not last attempt
            if attempt < max_restart_attempts {
                tracing::info!("Restarting server for retry");
                if let Err(e) = adapter.restart_server(&panel_server_id).await {
                    tracing::error!("Failed to restart server: {}", e);
                }
                tokio::time::sleep(tokio::time::Duration::from_secs(15)).await;
            }
        }

        if !health_passed {
            anyhow::bail!("Post-wipe health checks failed after {} attempts", max_restart_attempts);
        }

        // Execute post-wipe commands
        if let Some(post_commands) = config.get("post_wipe_commands").and_then(|v| v.as_array()) {
            for cmd in post_commands {
                if let Some(cmd_str) = cmd.as_str() {
                    tracing::debug!("Running post-wipe command: {}", cmd_str);
                    if let Err(e) = adapter.send_command(&panel_server_id, cmd_str).await {
                        tracing::warn!("Post-wipe command failed: {}", e);
                    }
                }
            }
        }

        // Calculate wipe duration
        let duration: Option<(chrono::DateTime<chrono::Utc>,)> = sqlx::query_as(
            "SELECT started_at FROM wipe_history WHERE id = $1"
        )
        .bind(wipe_history_id)
        .fetch_optional(&self.db)
        .await?;

        let duration_seconds = if let Some((started_at,)) = duration {
            (chrono::Utc::now() - started_at).num_seconds() as u64
        } else {
            0
        };

        // Send Discord post-announce notification
        if discord_post_announce {
            if let Ok(notifier) = self.get_discord_notifier(license_id, &server_name).await {
                // Get map and seed info
                let map_seed: Option<(Option<i32>,)> = sqlx::query_as(
                    "SELECT current_seed FROM server_config WHERE license_id = $1"
                )
                .bind(license_id)
                .fetch_optional(&self.db)
                .await?;

                let seed = map_seed.and_then(|(s,)| s);

                if let Err(e) = notifier.send_wipe_complete(&wipe_type, duration_seconds, None, seed).await {
                    tracing::warn!("Failed to send Discord post-wipe notification: {}", e);
                }
            }
        }

        // Send Pushbullet notification
        if pushbullet_notify {
            if let Ok(notifier) = self.get_pushbullet_notifier(license_id).await {
                if let Err(e) = notifier.send_wipe_complete(&server_name, &wipe_type).await {
                    tracing::warn!("Failed to send Pushbullet notification: {}", e);
                }
            }
        }

        tracing::info!("Post-wipe verification completed successfully");
        Ok(())
    }

    /// Execute rollback: restore pre-wipe backup, restart server,
    /// notify operators of failure.
    pub async fn execute_rollback(&self, wipe_history_id: Uuid) -> Result<()> {
        use anyhow::Context;

        tracing::warn!("Executing rollback for wipe {}", wipe_history_id);

        // Load wipe record with backup reference
        let record: Option<(Uuid, String, Option<String>, Option<String>, Option<String>)> = sqlx::query_as(
            "SELECT wh.license_id, wh.wipe_type, wh.backup_reference, wh.error_message, sc.server_name
             FROM wipe_history wh
             LEFT JOIN server_config sc ON sc.license_id = wh.license_id
             WHERE wh.id = $1",
        )
        .bind(wipe_history_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load wipe record for rollback")?;

        let (license_id, wipe_type, backup_reference, error_message, server_name_opt) = record
            .context("Wipe record not found")?;

        let server_name = server_name_opt.unwrap_or_else(|| "Server".to_string());
        let error_message = error_message.unwrap_or_else(|| "Unknown error".to_string());

        let backup_ref = backup_reference
            .context("No backup reference found - cannot rollback")?;

        // Load server connection
        let server_conn: Option<(String, String)> = sqlx::query_as(
            "SELECT connection_type, panel_server_identifier
             FROM server_connections
             WHERE license_id = $1",
        )
        .bind(license_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to load server connection")?;

        let (connection_type, panel_server_id) = server_conn
            .context("Server connection not configured")?;

        // Resolve panel adapter
        let adapter = self.resolve_panel_adapter(license_id, &connection_type).await?;

        // Stop the server
        tracing::info!("Stopping server for rollback");
        if let Err(e) = adapter.stop_server(&panel_server_id).await {
            tracing::warn!("Failed to stop server for rollback: {}", e);
        }
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;

        // Restore backup
        tracing::info!("Restoring backup: {}", backup_ref);
        let backup_manager = super::backup_manager::BackupManager::new(
            self.db.clone(),
            std::env::var("BACKUP_STORAGE_PATH").unwrap_or_else(|_| "/var/lib/corrosion/backups".to_string())
        );

        backup_manager.restore_backup(adapter.as_ref(), &panel_server_id, &backup_ref).await
            .context("Failed to restore backup during rollback")?;

        // Start the server
        tracing::info!("Starting server after rollback");
        adapter.start_server(&panel_server_id).await
            .context("Failed to start server after rollback")?;

        // Wait for server to start
        tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;

        // Verify server health post-rollback
        let health_checker = super::health_checker::HealthChecker::new(self.db.clone());
        match health_checker.verify_server_health(adapter.as_ref(), &panel_server_id, license_id).await {
            Ok(true) => {
                tracing::info!("Server health verified after rollback");
            }
            Ok(false) => {
                tracing::error!("Server health check failed after rollback");
            }
            Err(e) => {
                tracing::error!("Health check error after rollback: {}", e);
            }
        }

        // Update wipe_history status to 'rolled_back'
        sqlx::query(
            "UPDATE wipe_history
             SET status = 'rolled_back',
                 execution_log = execution_log || $1::jsonb
             WHERE id = $2",
        )
        .bind(serde_json::json!([{
            "timestamp": chrono::Utc::now().to_rfc3339(),
            "phase": "rollback",
            "message": "Backup restored due to wipe failure"
        }]))
        .bind(wipe_history_id)
        .execute(&self.db)
        .await
        .context("Failed to update wipe status to rolled_back")?;

        // Publish NATS event
        self.publish_status_event(license_id, wipe_history_id, "rolled_back", &wipe_type).await;

        // Send failure notifications
        if let Ok(notifier) = self.get_discord_notifier(license_id, &server_name).await {
            if let Err(e) = notifier.send_wipe_failed(&wipe_type, &error_message, true).await {
                tracing::warn!("Failed to send Discord rollback notification: {}", e);
            }
        }

        if let Ok(notifier) = self.get_pushbullet_notifier(license_id).await {
            if let Err(e) = notifier.send_wipe_failed(&server_name, &error_message).await {
                tracing::warn!("Failed to send Pushbullet rollback notification: {}", e);
            }
        }

        tracing::warn!("Rollback completed for wipe {}", wipe_history_id);
        Ok(())
    }

    // --- Helper methods ---

    /// Mark a wipe as failed and update the error message.
    async fn mark_wipe_failed(&self, wipe_history_id: Uuid, error: &str) -> Result<()> {
        sqlx::query(
            "UPDATE wipe_history
             SET status = 'failed', error_message = $1, completed_at = NOW(),
                 execution_log = execution_log || $2::jsonb
             WHERE id = $3",
        )
        .bind(error)
        .bind(serde_json::json!([{
            "timestamp": chrono::Utc::now().to_rfc3339(),
            "phase": "failure",
            "message": error
        }]))
        .bind(wipe_history_id)
        .execute(&self.db)
        .await?;

        Ok(())
    }

    /// Publish a NATS event for wipe status change.
    async fn publish_status_event(&self, license_id: Uuid, wipe_history_id: Uuid, status: &str, wipe_type: &str) {
        let subject = format!("corrosion.wipes.{}.status", license_id);
        let event_payload = serde_json::json!({
            "wipe_history_id": wipe_history_id,
            "license_id": license_id,
            "status": status,
            "wipe_type": wipe_type,
            "timestamp": chrono::Utc::now().to_rfc3339()
        });

        if let Err(e) = self.nats.publish(subject, event_payload.to_string().into()).await {
            tracing::warn!("Failed to publish NATS event: {}", e);
        }
    }

    /// Resolve the appropriate PanelAdapter for a given license.
    async fn resolve_panel_adapter(
        &self,
        license_id: Uuid,
        connection_type: &str,
    ) -> Result<Box<dyn super::panel_adapter::PanelAdapter>> {
        use anyhow::Context;

        match connection_type {
            "amp" => {
                let conn: Option<(String, String)> = sqlx::query_as(
                    "SELECT panel_api_endpoint, panel_api_key_encrypted
                     FROM server_connections WHERE license_id = $1",
                )
                .bind(license_id)
                .fetch_optional(&self.db)
                .await
                .context("Failed to query AMP connection details")?;

                let (endpoint, _encrypted_key) = conn
                    .context("AMP connection details not found")?;

                // In production, decrypt the API key here
                // For now, use a placeholder
                let api_key = "decrypted_key".to_string();

                Ok(Box::new(super::amp_adapter::AmpAdapter::new(
                    endpoint,
                    "admin".to_string(),
                    api_key,
                )))
            }
            "pterodactyl" => {
                let conn: Option<(String, String)> = sqlx::query_as(
                    "SELECT panel_api_endpoint, panel_api_key_encrypted
                     FROM server_connections WHERE license_id = $1",
                )
                .bind(license_id)
                .fetch_optional(&self.db)
                .await
                .context("Failed to query Pterodactyl connection details")?;

                let (endpoint, _encrypted_key) = conn
                    .context("Pterodactyl connection details not found")?;

                let api_key = "decrypted_key".to_string();

                Ok(Box::new(super::pterodactyl_adapter::PterodactylAdapter::new(
                    endpoint,
                    api_key,
                )))
            }
            "bare_metal" => {
                // Companion agent uses NATS for communication
                anyhow::bail!("Bare metal / companion agent support not yet implemented")
            }
            _ => {
                anyhow::bail!("Unsupported connection type: {}", connection_type)
            }
        }
    }

    /// Get Discord notifier for a license.
    async fn get_discord_notifier(&self, license_id: Uuid, server_name: &str) -> Result<super::discord::DiscordNotifier> {
        use anyhow::Context;

        let config: Option<(String, bool)> = sqlx::query_as(
            "SELECT discord_webhook_url, discord_enabled
             FROM notifications_config
             WHERE license_id = $1",
        )
        .bind(license_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to query Discord config")?;

        if let Some((webhook_url, enabled)) = config {
            if enabled {
                return Ok(super::discord::DiscordNotifier::new(webhook_url, server_name.to_string()));
            }
        }

        anyhow::bail!("Discord notifications not configured or disabled")
    }

    /// Get Pushbullet notifier for a license.
    async fn get_pushbullet_notifier(&self, license_id: Uuid) -> Result<super::pushbullet::PushbulletNotifier> {
        use anyhow::Context;

        let config: Option<(String, bool)> = sqlx::query_as(
            "SELECT pushbullet_api_key, pushbullet_enabled
             FROM notifications_config
             WHERE license_id = $1",
        )
        .bind(license_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to query Pushbullet config")?;

        if let Some((api_key, enabled)) = config {
            if enabled {
                return Ok(super::pushbullet::PushbulletNotifier::new(api_key));
            }
        }

        anyhow::bail!("Pushbullet notifications not configured or disabled")
    }
}
