//! Service Manager Implementation
//!
//! Calls Nushell service management commands to start/stop platform services.

use std::process::Stdio;

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use tokio::process::Command;
use tracing::{debug, error};

/// Service definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Service {
    pub name: String,
    pub description: String,
    pub dependencies: Vec<String>,
    pub start_command: String,
    pub stop_command: String,
    pub health_check_endpoint: Option<String>,
}

/// Service status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ServiceStatus {
    Running,
    Stopped,
    Failed,
    Unknown,
}

impl std::fmt::Display for ServiceStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ServiceStatus::Running => write!(f, "running"),
            ServiceStatus::Stopped => write!(f, "stopped"),
            ServiceStatus::Failed => write!(f, "failed"),
            ServiceStatus::Unknown => write!(f, "unknown"),
        }
    }
}

/// Health status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthStatus {
    pub healthy: bool,
    pub message: String,
    pub last_check: chrono::DateTime<chrono::Utc>,
}

/// Service manager - calls Nushell service commands
pub struct ServiceManager {
    nu_path: String,
    provisioning_path: String,
    client: reqwest::Client,
}

impl ServiceManager {
    /// Create new service manager
    pub fn new(nu_path: String, provisioning_path: String) -> Self {
        Self {
            nu_path,
            provisioning_path,
            client: reqwest::Client::new(),
        }
    }

    /// Start service
    pub async fn start_service(&self, service_name: &str) -> Result<()> {
        debug!("Starting service: {}", service_name);

        let args = vec![
            "service".to_string(),
            "start".to_string(),
            service_name.to_string(),
        ];

        self.execute_service_command(&args).await?;

        debug!("Successfully started service: {}", service_name);

        Ok(())
    }

    /// Stop service
    pub async fn stop_service(&self, service_name: &str) -> Result<()> {
        debug!("Stopping service: {}", service_name);

        let args = vec![
            "service".to_string(),
            "stop".to_string(),
            service_name.to_string(),
        ];

        self.execute_service_command(&args).await?;

        debug!("Successfully stopped service: {}", service_name);

        Ok(())
    }

    /// Get service status
    pub async fn get_service_status(&self, service_name: &str) -> Result<ServiceStatus> {
        debug!("Getting status for service: {}", service_name);

        let args = vec![
            "service".to_string(),
            "status".to_string(),
            service_name.to_string(),
            "--output".to_string(),
            "json".to_string(),
        ];

        let output = self.execute_service_command(&args).await?;

        #[derive(Deserialize)]
        struct StatusResponse {
            status: String,
        }

        let response: StatusResponse =
            serde_json::from_str(&output).context("Failed to parse service status")?;

        let status = match response.status.as_str() {
            "running" => ServiceStatus::Running,
            "stopped" => ServiceStatus::Stopped,
            "failed" => ServiceStatus::Failed,
            _ => ServiceStatus::Unknown,
        };

        debug!("Service {} status: {}", service_name, status);

        Ok(status)
    }

    /// Check service health
    pub async fn check_service_health(&self, service_name: &str) -> Result<HealthStatus> {
        debug!("Checking health for service: {}", service_name);

        // First check if service is running
        let status = self.get_service_status(service_name).await?;

        if status != ServiceStatus::Running {
            return Ok(HealthStatus {
                healthy: false,
                message: format!("Service is {}", status),
                last_check: chrono::Utc::now(),
            });
        }

        // Try HTTP health check if endpoint is available
        // For now, just return healthy if service is running
        Ok(HealthStatus {
            healthy: true,
            message: "Service is running".to_string(),
            last_check: chrono::Utc::now(),
        })
    }

    /// Check health via HTTP endpoint
    pub async fn check_http_health(&self, endpoint: &str) -> Result<bool> {
        debug!("Checking HTTP health: {}", endpoint);

        match self.client.get(endpoint).send().await {
            Ok(response) => {
                let is_healthy = response.status().is_success();
                debug!("HTTP health check for {}: {}", endpoint, is_healthy);
                Ok(is_healthy)
            }
            Err(e) => {
                error!("HTTP health check failed for {}: {}", endpoint, e);
                Ok(false)
            }
        }
    }

    /// Restart service
    pub async fn restart_service(&self, service_name: &str) -> Result<()> {
        debug!("Restarting service: {}", service_name);

        // Stop first
        self.stop_service(service_name).await?;

        // Wait a bit
        tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

        // Start again
        self.start_service(service_name).await?;

        debug!("Successfully restarted service: {}", service_name);

        Ok(())
    }

    /// Execute service command via Nushell
    async fn execute_service_command(&self, args: &[String]) -> Result<String> {
        let command_str = format!("{} {}", self.provisioning_path, args.join(" "));

        debug!("Executing: nu -c '{}'", command_str);

        let mut cmd = Command::new(&self.nu_path);
        cmd.arg("-c")
            .arg(&command_str)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());

        let output = cmd
            .output()
            .await
            .context("Failed to execute Nushell command")?;

        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout).to_string();
            Ok(stdout.trim().to_string())
        } else {
            let stderr = String::from_utf8_lossy(&output.stderr);
            error!("Service command failed: {}", stderr);
            anyhow::bail!("Service command failed: {}", stderr)
        }
    }
}

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

    #[test]
    fn test_service_status_display() {
        assert_eq!(ServiceStatus::Running.to_string(), "running");
        assert_eq!(ServiceStatus::Stopped.to_string(), "stopped");
        assert_eq!(ServiceStatus::Failed.to_string(), "failed");
        assert_eq!(ServiceStatus::Unknown.to_string(), "unknown");
    }

    #[test]
    fn test_service_creation() {
        let service = Service {
            name: "test-service".to_string(),
            description: "Test service".to_string(),
            dependencies: vec!["dep1".to_string(), "dep2".to_string()],
            start_command: "start test".to_string(),
            stop_command: "stop test".to_string(),
            health_check_endpoint: Some("http://localhost:8080/health".to_string()),
        };

        assert_eq!(service.name, "test-service");
        assert_eq!(service.dependencies.len(), 2);
        assert!(service.health_check_endpoint.is_some());
    }

    #[test]
    fn test_service_manager_creation() {
        let manager = ServiceManager::new(
            "/usr/local/bin/nu".to_string(),
            "/usr/local/bin/provisioning".to_string(),
        );

        assert_eq!(manager.nu_path, "/usr/local/bin/nu");
        assert_eq!(manager.provisioning_path, "/usr/local/bin/provisioning");
    }

    #[test]
    fn test_health_status_creation() {
        let health = HealthStatus {
            healthy: true,
            message: "All systems operational".to_string(),
            last_check: chrono::Utc::now(),
        };

        assert!(health.healthy);
        assert_eq!(health.message, "All systems operational");
    }
}
