use crate::context::ContextSession;
/**
 * A module to enable us to run Moose commands for the user
 */
use anyhow::{bail, Result};
use spinners::{Spinner, Spinners};
use std::fmt::Display;

use super::execute_command;

// Check if we can run npx
fn can_run_npx() -> bool {
    execute_command("npx", Some(&["--version"])).is_ok()
}

fn has_node() -> bool {
    execute_command("node", Some(&["--version"])).is_ok()
}

fn can_run_pip() -> bool {
    execute_command("pip", Some(&["--version"])).is_ok()
}

fn has_docker() -> bool {
    execute_command("docker", Some(&["--version"])).is_ok()
}

fn has_git() -> bool {
    execute_command("git", Some(&["--version"])).is_ok()
}

fn has_python_312() -> bool {
    execute_command("python3.12", Some(&["--version"])).is_ok()
}

pub fn check_shared_dependencies() -> Result<()> {
    let mut errors = Vec::new();

    if !has_git() {
        errors.push("git is not installed".to_string());
    }

    if !has_docker() {
        errors.push("docker is not installed".to_string());
    }

    if !errors.is_empty() {
        bail!(
            "The following dependencies are not met: {}",
            errors.join(", ")
        );
    }

    Ok(())
}

pub fn check_ts_dependencies() -> Result<()> {
    let mut errors = Vec::new();

    if !has_node() {
        errors.push("node is not installed".to_string());
    }

    if !can_run_npx() {
        errors.push("npx is not installed".to_string());
    }

    if !errors.is_empty() {
        bail!(
            "The following dependencies are not met: {}",
            errors.join(", ")
        );
    }

    Ok(())
}

pub fn check_python_dependencies() -> Result<()> {
    let mut errors = Vec::new();

    if !has_python_312() {
        errors.push("python 3.12 is not installed".to_string());
    }

    if !can_run_pip() {
        errors.push("pip is not installed".to_string());
    }

    if !errors.is_empty() {
        bail!(
            "The following dependencies are not met: {}",
            errors.join(", ")
        );
    }

    Ok(())
}

/// Check if the moose CLI is installed globally
pub fn has_moose_cli() -> Result<bool> {
    Ok(execute_command("moose-cli", Some(&["--version"])).is_ok())
}

pub fn is_valid_moose_project_with_cli() -> Result<bool> {
    match execute_command("moose-cli", Some(&["check"])) {
        Ok(_) => Ok(true),
        Err(e) => bail!("Moose check failed: {}", e),
    }
}

/// Run a moose check command to see if the current directory is a moose project
///
/// This assumes npm by default to run the command but will fall back to python if npm is not available
pub fn is_moose_project() -> Result<bool> {
    match check_ts_dependencies() {
        Ok(_) => match has_moose_cli() {
            Ok(true) => is_valid_moose_project_with_cli(),
            Ok(false) => {
                install_moose_cli(Language::Typescript)?;
                is_valid_moose_project_with_cli()
            }
            Err(e) => bail!("Failed to check for moose CLI: {}", e),
        },
        Err(_) => match check_python_dependencies() {
            Ok(_) => match has_moose_cli() {
                Ok(true) => is_valid_moose_project_with_cli(),
                Ok(false) => {
                    install_moose_cli(Language::Python)?;
                    is_valid_moose_project_with_cli()
                }
                Err(e) => bail!("Failed to check for moose CLI: {}", e),
            },
            Err(e) => bail!("Failed to check for python dependencies: {}", e),
        },
    }
}

// Globally install the moose CLI  using pip or npm depending on the language
fn install_moose_cli(language: Language) -> Result<()> {
    let mut llm_spinner = Spinner::new(Spinners::Dots, "Installing the Moose CLI...".to_string());
    match language {
        Language::Python => execute_command("pip", Some(&["install", "@514labs/moose-cli"])),
        Language::Typescript => {
            execute_command("npm", Some(&["install", "-g", "@514labs/moose-cli"]))
        }
    }?;
    llm_spinner.stop_and_persist("✔", "Moose CLI installed!".to_string());
    Ok(())
}

#[derive(Debug, Clone)]
pub enum Language {
    Python,
    Typescript,
}

impl Display for Language {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Language::Python => write!(f, "python"),
            Language::Typescript => write!(f, "typescript"),
        }
    }
}

pub fn create_empty_moose_project(
    project_name: &str,
    language: Language,
    context_session: &mut ContextSession,
) -> Result<()> {
    execute_command(
        "npx",
        Some(&[
            "@514labs/moose-cli",
            "init",
            project_name,
            language.to_string().as_str(),
            "--empty",
        ]),
    )?;

    // Save project path to context
    context_session.add_text_to_context("project_path", project_name);
    Ok(())
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::*;
    use tempfile::tempdir;

    #[test]
    fn test_create_moose_project() -> Result<()> {
        // Create a temporary directory for testing
        let temp_dir = tempdir()?;
        let temp_path = temp_dir.path();

        let mut context_session = ContextSession::new()?;

        // Change to the temporary directory
        std::env::set_current_dir(temp_path)?;

        // Create a test project
        let project_name = "test-moose-project";
        create_empty_moose_project(project_name, Language::Python, &mut context_session)?;

        // Verify project directory was created
        let project_path = temp_path.join(project_name);
        assert!(project_path.exists(), "Project directory was not created");

        // Verify project structure based on Moose documentation
        let expected_structure = vec![
            "app",
            "app/datamodels",
            "app/functions",
            "app/blocks",
            "app/apis",
            "moose.config.toml",
        ];

        for path in expected_structure {
            let file_path = project_path.join(path);
            assert!(
                file_path.exists(),
                "Expected path {} not found in project",
                path
            );
        }

        // Verify moose.config.toml exists
        assert!(
            project_path.join("moose.config.toml").exists(),
            "moose.config.toml not found"
        );

        Ok(())
    }

    #[test]
    fn test_is_moose_project() -> Result<()> {
        // Create a temporary directory for testing
        let temp_dir = tempdir()?;
        let temp_path = temp_dir.path();

        // Change to the temporary directory
        std::env::set_current_dir(temp_path)?;

        let mut context_session = ContextSession::new()?;

        // Create a test project
        let project_name = "test-moose-project";
        create_empty_moose_project(project_name, Language::Python, &mut context_session)?;

        // Verify project directory was created
        let project_path = temp_path.join(project_name);
        assert!(project_path.exists(), "Project directory was not created");

        // Move to the project directory
        std::env::set_current_dir(project_path)?;

        // Verify moose.config.toml exists in the current directory
        assert!(
            Path::new("moose.config.toml").exists(),
            "moose.config.toml not found"
        );

        // Check if the project is a moose project
        assert!(is_moose_project()?);

        Ok(())
    }

    #[test]
    fn test_check_shared_dependencies() {
        check_shared_dependencies().unwrap();
    }

    #[test]
    fn test_check_ts_dependencies() {
        check_ts_dependencies().unwrap();
    }

    #[test]
    fn test_check_python_dependencies() {
        check_python_dependencies().unwrap();
    }
}
