//! Nickel CLI wrapper using std::process::Command
//!
//! Provides safe wrappers around nickel CLI commands:
//! - `nickel query` - Extract metadata from .ncl files
//! - `nickel export` - Export evaluated Nickel to JSON
//! - `nickel typecheck` - Validate Nickel syntax and types

use crate::error::ErrorWrapper;
use crate::Result;
use std::path::Path;
use std::process::Command;

/// Nickel CLI command wrapper
pub struct NickelCli;

impl NickelCli {
    /// Verify that nickel CLI is installed and accessible
    ///
    /// # Errors
    ///
    /// Returns an error if `nickel` command is not found or cannot be executed.
    pub fn verify() -> Result<String> {
        let output = Command::new("nickel")
            .arg("--version")
            .output()
            .map_err(|e| {
                ErrorWrapper::new(format!(
                    "Failed to execute 'nickel --version'. \
                         Is nickel installed? Install from: https://nickel-lang.org/install\n\
                         Error: {}",
                    e
                ))
            })?;

        if !output.status.success() {
            return Err(ErrorWrapper::new(
                "nickel command failed. Is nickel installed correctly?",
            ));
        }

        String::from_utf8(output.stdout).map_err(|e| {
            ErrorWrapper::new(format!("Invalid UTF-8 in nickel version output: {}", e))
        })
    }

    /// Execute `nickel query --format json` to extract metadata
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the .ncl file
    /// * `field` - Optional field path to query (e.g., "inputs", "inputs.user")
    ///
    /// # Returns
    ///
    /// JSON value containing the queried metadata
    ///
    /// # Errors
    ///
    /// Returns an error if the nickel command fails or output is invalid JSON.
    pub fn query(path: &Path, field: Option<&str>) -> Result<serde_json::Value> {
        let mut cmd = Command::new("nickel");
        cmd.arg("query").arg("--format").arg("json").arg(path);

        if let Some(f) = field {
            cmd.arg("--field").arg(f);
        }

        let output = cmd.output().map_err(|e| {
            ErrorWrapper::new(format!(
                "Failed to execute 'nickel query' on {}: {}",
                path.display(),
                e
            ))
        })?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(ErrorWrapper::new(format!(
                "nickel query failed for {}: {}",
                path.display(),
                stderr
            )));
        }

        let stdout = String::from_utf8(output.stdout).map_err(|e| {
            ErrorWrapper::new(format!("Invalid UTF-8 in nickel query output: {}", e))
        })?;

        serde_json::from_str(&stdout).map_err(|e| {
            ErrorWrapper::new(format!(
                "Failed to parse nickel query output as JSON: {}",
                e
            ))
        })
    }

    /// Execute `nickel export --format json` to export evaluated Nickel
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the .ncl file
    ///
    /// # Returns
    ///
    /// JSON value containing the exported Nickel configuration
    ///
    /// # Errors
    ///
    /// Returns an error if the nickel command fails or output is invalid JSON.
    pub fn export(path: &Path) -> Result<serde_json::Value> {
        let output = Command::new("nickel")
            .arg("export")
            .arg("--format")
            .arg("json")
            .arg(path)
            .output()
            .map_err(|e| {
                ErrorWrapper::new(format!(
                    "Failed to execute 'nickel export' on {}: {}",
                    path.display(),
                    e
                ))
            })?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(ErrorWrapper::new(format!(
                "nickel export failed for {}: {}",
                path.display(),
                stderr
            )));
        }

        let stdout = String::from_utf8(output.stdout).map_err(|e| {
            ErrorWrapper::new(format!("Invalid UTF-8 in nickel export output: {}", e))
        })?;

        serde_json::from_str(&stdout).map_err(|e| {
            ErrorWrapper::new(format!(
                "Failed to parse nickel export output as JSON: {}",
                e
            ))
        })
    }

    /// Execute `nickel typecheck` to validate Nickel file
    ///
    /// # Arguments
    ///
    /// * `path` - Path to the .ncl file to typecheck
    ///
    /// # Returns
    ///
    /// Ok if typecheck succeeds, Err with detailed message if it fails
    ///
    /// # Errors
    ///
    /// Returns an error if the nickel typecheck command fails.
    pub fn typecheck(path: &Path) -> Result<()> {
        // Execute typecheck from the file's directory to allow relative imports to resolve
        let parent_dir = path.parent().unwrap_or_else(|| std::path::Path::new("."));
        let filename = path
            .file_name()
            .ok_or_else(|| ErrorWrapper::new("Cannot extract filename from path".to_string()))?;

        let output = Command::new("nickel")
            .current_dir(parent_dir)
            .arg("typecheck")
            .arg(filename)
            .output()
            .map_err(|e| {
                ErrorWrapper::new(format!(
                    "Failed to execute 'nickel typecheck' on {}: {}",
                    path.display(),
                    e
                ))
            })?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(ErrorWrapper::new(format!(
                "nickel typecheck failed for {}:\n{}",
                path.display(),
                stderr
            )));
        }

        Ok(())
    }
}

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

    #[test]
    fn test_verify_nickel_installed() {
        // This will fail if nickel is not installed, which is expected
        let result = NickelCli::verify();
        if result.is_ok() {
            println!("Nickel is installed: {:?}", result);
        } else {
            eprintln!("Nickel not found: {:?}", result);
        }
    }
}
