//! Extension Loader Implementation
//!
//! Calls Nushell scripts to load extensions dynamically.

use std::process::Stdio;

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

/// Extension types
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ExtensionType {
    Provider,
    Taskserv,
    Cluster,
}

impl std::fmt::Display for ExtensionType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ExtensionType::Provider => write!(f, "provider"),
            ExtensionType::Taskserv => write!(f, "taskserv"),
            ExtensionType::Cluster => write!(f, "cluster"),
        }
    }
}

/// Extension metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtensionMetadata {
    pub name: String,
    pub version: String,
    pub description: String,
    pub extension_type: ExtensionType,
    pub dependencies: Vec<String>,
    pub author: Option<String>,
    pub repository: Option<String>,
}

/// Loaded extension
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Extension {
    pub metadata: ExtensionMetadata,
    pub path: String,
    pub loaded_at: chrono::DateTime<chrono::Utc>,
}

/// Extension loader - calls Nushell scripts
pub struct ExtensionLoader {
    nu_path: String,
    provisioning_path: String,
}

impl ExtensionLoader {
    /// Create new extension loader
    pub fn new(nu_path: String, provisioning_path: String) -> Self {
        Self {
            nu_path,
            provisioning_path,
        }
    }

    /// Load extension via Nushell
    pub async fn load_extension(
        &self,
        extension_type: ExtensionType,
        name: String,
        version: Option<String>,
    ) -> Result<Extension> {
        debug!(
            "Loading extension: {} ({}) version {:?}",
            name, extension_type, version
        );

        // Build Nushell command to load extension
        let mut args = vec![
            "module".to_string(),
            "load".to_string(),
            extension_type.to_string(),
            name.clone(),
        ];

        if let Some(v) = version.as_ref() {
            args.push("--version".to_string());
            args.push(v.clone());
        }

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

        // Parse output to get extension metadata
        let metadata: ExtensionMetadata =
            serde_json::from_str(&output).context("Failed to parse extension metadata")?;

        Ok(Extension {
            metadata,
            path: format!("extensions/{}s/{}", extension_type, name),
            loaded_at: chrono::Utc::now(),
        })
    }

    /// Get extension metadata
    pub async fn get_extension_metadata(
        &self,
        extension_type: ExtensionType,
        name: String,
    ) -> Result<ExtensionMetadata> {
        debug!("Getting metadata for: {} ({})", name, extension_type);

        let args = vec![
            "module".to_string(),
            "discover".to_string(),
            extension_type.to_string(),
            "--name".to_string(),
            name,
            "--output".to_string(),
            "json".to_string(),
        ];

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

        let metadata: ExtensionMetadata =
            serde_json::from_str(&output).context("Failed to parse extension metadata")?;

        Ok(metadata)
    }

    /// List available extensions
    pub async fn list_available_extensions(
        &self,
        extension_type: ExtensionType,
    ) -> Result<Vec<String>> {
        debug!("Listing available {} extensions", extension_type);

        let args = vec![
            "module".to_string(),
            "discover".to_string(),
            extension_type.to_string(),
            "--output".to_string(),
            "json".to_string(),
        ];

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

        let extensions: Vec<String> =
            serde_json::from_str(&output).context("Failed to parse extension list")?;

        debug!("Found {} available extensions", extensions.len());

        Ok(extensions)
    }

    /// Execute provisioning command via Nushell
    async fn execute_provisioning_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!("Command failed: {}", stderr);
            anyhow::bail!("Extension loading failed: {}", stderr)
        }
    }
}

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

    #[test]
    fn test_extension_type_display() {
        assert_eq!(ExtensionType::Provider.to_string(), "provider");
        assert_eq!(ExtensionType::Taskserv.to_string(), "taskserv");
        assert_eq!(ExtensionType::Cluster.to_string(), "cluster");
    }

    #[test]
    fn test_extension_metadata_creation() {
        let metadata = ExtensionMetadata {
            name: "kubernetes".to_string(),
            version: "1.28.0".to_string(),
            description: "Kubernetes container orchestrator".to_string(),
            extension_type: ExtensionType::Taskserv,
            dependencies: vec!["containerd".to_string(), "etcd".to_string()],
            author: Some("provisioning-team".to_string()),
            repository: None,
        };

        assert_eq!(metadata.name, "kubernetes");
        assert_eq!(metadata.dependencies.len(), 2);
    }

    #[test]
    fn test_extension_loader_creation() {
        let loader = ExtensionLoader::new(
            "/usr/local/bin/nu".to_string(),
            "/usr/local/bin/provisioning".to_string(),
        );

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