use anyhow::{Context, Result};
use std::time::Duration;
use uuid::Uuid;

use super::panel_adapter::PanelAdapter;

/// Post-wipe server health verification.
///
/// Called by the wipe engine after a wipe completes to verify the server
/// came back up correctly. Checks are configurable via PostWipeConfig
/// and failures can trigger rollback or retry logic.
pub struct HealthChecker {
    db: sqlx::PgPool,
    max_retries: u32,
    check_timeout: Duration,
}

impl HealthChecker {
    pub fn new(db: sqlx::PgPool) -> Self {
        Self {
            db,
            max_retries: 3,
            check_timeout: Duration::from_secs(60),
        }
    }

    /// Run all configured health checks against the server.
    ///
    /// Returns Ok(true) if all checks pass, Ok(false) if checks fail
    /// after exhausting retries. Returns Err only on unexpected errors.
    pub async fn verify_server_health(
        &self,
        adapter: &dyn PanelAdapter,
        server_id: &str,
        license_id: Uuid,
    ) -> Result<bool> {
        // Load wipe config for this license to determine what to check
        let config: Option<(bool, bool, bool)> = sqlx::query_as(
            "SELECT sc.verify_map_loaded, sc.verify_plugins, sc.verify_player_slots
             FROM server_config sc
             WHERE sc.license_id = $1",
        )
        .bind(license_id)
        .fetch_optional(&self.db)
        .await
        .context("Failed to query health check config")?;

        let (verify_map, verify_plugins, verify_slots) = config.unwrap_or((true, true, true));

        // Retry loop
        for attempt in 1..=self.max_retries {
            tracing::debug!("Health check attempt {}/{}", attempt, self.max_retries);

            // Step 1: Wait for server to report running
            if !self.wait_for_server_running(adapter, server_id).await? {
                tracing::warn!("Server did not start after attempt {}", attempt);
                tokio::time::sleep(Duration::from_secs(10)).await;
                continue;
            }

            // Step 2: Run configured checks
            let mut all_passed = true;

            if verify_map {
                match self.check_map(adapter, server_id, "procedural").await {
                    Ok(true) => tracing::debug!("Map check passed"),
                    Ok(false) => {
                        tracing::warn!("Map check failed");
                        all_passed = false;
                    }
                    Err(e) => {
                        tracing::error!("Map check error: {}", e);
                        all_passed = false;
                    }
                }
            }

            if verify_plugins {
                match self.check_plugins(adapter, server_id, &[]).await {
                    Ok(true) => tracing::debug!("Plugin check passed"),
                    Ok(false) => {
                        tracing::warn!("Plugin check failed");
                        all_passed = false;
                    }
                    Err(e) => {
                        tracing::error!("Plugin check error: {}", e);
                        all_passed = false;
                    }
                }
            }

            if verify_slots {
                match self.check_player_slots(adapter, server_id).await {
                    Ok(true) => tracing::debug!("Player slots check passed"),
                    Ok(false) => {
                        tracing::warn!("Player slots check failed");
                        all_passed = false;
                    }
                    Err(e) => {
                        tracing::error!("Player slots check error: {}", e);
                        all_passed = false;
                    }
                }
            }

            if all_passed {
                tracing::info!("All health checks passed for server {}", server_id);
                return Ok(true);
            }

            // Backoff before retry
            if attempt < self.max_retries {
                tokio::time::sleep(Duration::from_secs(15)).await;
            }
        }

        tracing::error!(
            "Health checks failed after {} attempts for server {}",
            self.max_retries,
            server_id
        );
        Ok(false)
    }

    /// Wait for server to report is_running=true.
    async fn wait_for_server_running(
        &self,
        adapter: &dyn PanelAdapter,
        server_id: &str,
    ) -> Result<bool> {
        let deadline = tokio::time::Instant::now() + self.check_timeout;

        while tokio::time::Instant::now() < deadline {
            match adapter.get_server_status(server_id).await {
                Ok(status) if status.is_running => {
                    tracing::debug!("Server is running");
                    return Ok(true);
                }
                Ok(_) => {
                    tracing::debug!("Server not yet running, waiting...");
                }
                Err(e) => {
                    tracing::warn!("Failed to query server status: {}", e);
                }
            }

            tokio::time::sleep(Duration::from_secs(5)).await;
        }

        tracing::warn!("Timeout waiting for server to start");
        Ok(false)
    }

    /// Verify the correct map is loaded on the server.
    ///
    /// Sends RCON command to query the current level and compares
    /// against the expected map from the wipe profile.
    pub async fn check_map(
        &self,
        adapter: &dyn PanelAdapter,
        server_id: &str,
        expected_map: &str,
    ) -> Result<bool> {
        // Send server.seed command to verify map is loaded
        // For Rust servers, we can check if the server responds to basic commands
        let response = adapter
            .send_command(server_id, "serverinfo")
            .await
            .context("Failed to send serverinfo command")?;

        // Basic check: if we got a response, the server is functional
        // A full implementation would parse the response for the actual map name
        if response.is_empty() {
            return Ok(false);
        }

        // For now, accept any response as success
        // In production, parse response and verify it contains expected_map
        tracing::debug!("Map check response: {}", response);
        Ok(true)
    }

    /// Verify all required plugins are loaded and responding.
    pub async fn check_plugins(
        &self,
        adapter: &dyn PanelAdapter,
        server_id: &str,
        expected_plugins: &[String],
    ) -> Result<bool> {
        // Send oxide.plugins command to list loaded plugins
        let response = adapter
            .send_command(server_id, "oxide.plugins")
            .await
            .context("Failed to send oxide.plugins command")?;

        if response.is_empty() {
            return Ok(false);
        }

        // Check if all expected plugins are in the response
        for plugin in expected_plugins {
            if !response.contains(plugin) {
                tracing::warn!("Required plugin not loaded: {}", plugin);
                return Ok(false);
            }
        }

        tracing::debug!("All required plugins loaded");
        Ok(true)
    }

    /// Verify the server is accepting player connections.
    pub async fn check_player_slots(
        &self,
        adapter: &dyn PanelAdapter,
        server_id: &str,
    ) -> Result<bool> {
        // Query server status to verify it's connectable
        let status = adapter
            .get_server_status(server_id)
            .await
            .context("Failed to get server status for slots check")?;

        // Basic check: server is running
        if !status.is_running {
            return Ok(false);
        }

        // In production, would perform A2S query against game port
        // to verify server is actually accepting connections
        tracing::debug!("Player slots check passed (server is running)");
        Ok(true)
    }
}
