#![allow(dead_code)]

//! Simulation service for Godot scene generation and management
//!
//! This service provides operations for generating and managing Godot simulations,
//! including robot scene generation, environment selection, and validation.

use crate::paths;
use crate::sim::{EnvironmentSelector, RobotGenerator, RobotProfile};
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::process::Command;

/// Simulation service for managing Godot simulations
///
/// # Examples
///
/// ```rust,ignore
/// use mecha10_cli::services::SimulationService;
/// use std::path::Path;
///
/// # async fn example() -> anyhow::Result<()> {
/// let service = SimulationService::new();
///
/// // Validate Godot installation
/// service.validate_godot()?;
///
/// // Generate simulation
/// service.generate(
///     Path::new("mecha10.json"),
///     Path::new("simulation/godot"),
///     5, // max environments
///     0  // min score
/// )?;
///
/// // Run simulation
/// service.run(
///     "rover-robot",
///     "warehouse",
///     false // not headless
/// )?;
/// # Ok(())
/// # }
/// ```
pub struct SimulationService {
    /// Base path for simulation output
    base_path: PathBuf,
}

impl SimulationService {
    /// Create a new simulation service with default paths
    pub fn new() -> Self {
        Self {
            base_path: PathBuf::from(paths::project::SIMULATION_GODOT_DIR),
        }
    }

    /// Create a simulation service with custom base path
    ///
    /// # Arguments
    ///
    /// * `base_path` - Base directory for simulation output
    pub fn with_base_path(base_path: impl Into<PathBuf>) -> Self {
        Self {
            base_path: base_path.into(),
        }
    }

    /// Validate Godot installation
    ///
    /// Checks if Godot 4.x is installed and accessible.
    ///
    /// # Errors
    ///
    /// Returns an error if Godot is not found or version is incorrect.
    pub fn validate_godot(&self) -> Result<GodotInfo> {
        // Try common Godot executable locations
        let godot_paths = if cfg!(target_os = "macos") {
            vec![
                "/Applications/Godot.app/Contents/MacOS/Godot",
                "/usr/local/bin/godot",
                "/opt/homebrew/bin/godot",
            ]
        } else if cfg!(target_os = "linux") {
            vec!["/usr/bin/godot", "/usr/local/bin/godot", "/snap/bin/godot"]
        } else {
            vec!["godot.exe", "C:\\Program Files\\Godot\\godot.exe"]
        };

        // First try 'godot' command from PATH
        if let Ok(output) = Command::new("godot").arg("--version").output() {
            if output.status.success() {
                let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
                return Ok(GodotInfo {
                    path: "godot".to_string(),
                    version,
                    in_path: true,
                });
            }
        }

        // If not found in PATH, try specific locations
        for path in &godot_paths {
            if std::path::Path::new(path).exists() {
                if let Ok(output) = Command::new(path).arg("--version").output() {
                    if output.status.success() {
                        let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
                        return Ok(GodotInfo {
                            path: path.to_string(),
                            version,
                            in_path: false,
                        });
                    }
                }
            }
        }

        Err(anyhow::anyhow!(
            "Godot not found\n\n\
            Godot 4.x is required to generate and run simulations.\n\n\
            Installation instructions:\n\
            • macOS: brew install godot or download from https://godotengine.org/download/macos\n\
            • Linux: sudo apt install godot or download from https://godotengine.org/download/linux\n\
            • Windows: Download from https://godotengine.org/download/windows\n\n\
            After installation, ensure 'godot' is in your PATH or installed to a standard location."
        ))
    }

    /// Generate simulation scenes from project configuration
    ///
    /// # Arguments
    ///
    /// * `config_path` - Path to mecha10.json
    /// * `output_path` - Output directory for simulation files
    /// * `max_envs` - Maximum number of environments to generate
    /// * `min_score` - Minimum compatibility score (0-100)
    pub fn generate(
        &self,
        config_path: &Path,
        output_path: &Path,
        max_envs: usize,
        min_score: i32,
    ) -> Result<GenerationResult> {
        // Validate Godot first
        let godot_info = self.validate_godot()?;

        // Load robot profile
        let profile = RobotProfile::from_config_file(config_path)?;

        // Generate robot scene
        let robot_output = output_path.join("robot.tscn");
        let robot_generator = RobotGenerator::from_config_file(config_path)?;
        robot_generator.generate(&robot_output)?;

        // Select environments
        let selector = EnvironmentSelector::new()?;
        let matches = selector.select_environments(&profile, max_envs)?;

        let filtered_matches: Vec<_> = matches.into_iter().filter(|m| m.score >= min_score).collect();

        if filtered_matches.is_empty() {
            return Err(anyhow::anyhow!(
                "No matching environments found (min score: {})",
                min_score
            ));
        }

        // Count available environments
        let _catalog_path = PathBuf::from(paths::framework::ROBOT_TASKS_CATALOG);
        let base_path = PathBuf::from(paths::framework::ROBOT_TASKS_DIR);
        let mut available_count = 0;

        for env_match in &filtered_matches {
            let env_path = base_path.join(&env_match.environment.path);
            if env_path.exists() {
                available_count += 1;
            }
        }

        Ok(GenerationResult {
            robot_scene: robot_output,
            environments: filtered_matches.iter().map(|m| m.environment.id.clone()).collect(),
            available_count,
            godot_info,
        })
    }

    /// Generate only the robot scene
    ///
    /// # Arguments
    ///
    /// * `config_path` - Path to mecha10.json
    /// * `output_path` - Output path for robot.tscn
    pub fn generate_robot(&self, config_path: &Path, output_path: &Path) -> Result<()> {
        let robot_generator = RobotGenerator::from_config_file(config_path)?;
        robot_generator.generate(output_path)
    }

    /// List available environments
    ///
    /// # Arguments
    ///
    /// * `config_path` - Path to mecha10.json
    /// * `verbose` - Show detailed information
    pub fn list_environments(&self, config_path: &Path, verbose: bool) -> Result<Vec<EnvironmentInfo>> {
        let profile = RobotProfile::from_config_file(config_path)?;
        let selector = EnvironmentSelector::new()?;
        let matches = selector.select_environments(&profile, 100)?; // Get all

        let base_path = PathBuf::from(paths::framework::ROBOT_TASKS_DIR);
        let mut environments = Vec::new();

        for env_match in matches {
            let env_path = base_path.join(&env_match.environment.path);
            let available = env_path.exists();

            if verbose || available {
                environments.push(EnvironmentInfo {
                    id: env_match.environment.id.clone(),
                    name: env_match.environment.name.clone(),
                    description: env_match.environment.description.clone(),
                    score: env_match.score,
                    available,
                });
            }
        }

        Ok(environments)
    }

    /// Validate project configuration for simulation
    ///
    /// # Arguments
    ///
    /// * `config_path` - Path to mecha10.json
    pub fn validate_config(&self, config_path: &Path) -> Result<ValidationResult> {
        let profile = RobotProfile::from_config_file(config_path)?;

        let mut errors = Vec::new();
        let mut warnings = Vec::new();

        // Validate platform
        if profile.platform.is_empty() {
            errors.push("Platform not specified".to_string());
        }

        // Validate sensors
        if profile.sensors.is_empty() {
            warnings.push("No sensors configured".to_string());
        }

        // Validate task nodes
        if profile.task_nodes.is_empty() {
            warnings.push("No task nodes configured".to_string());
        }

        let is_valid = errors.is_empty();

        Ok(ValidationResult {
            is_valid,
            errors,
            warnings,
            platform: profile.platform,
            sensor_count: profile.sensors.len(),
            node_count: profile.task_nodes.len(),
        })
    }

    /// Run a simulation in Godot
    ///
    /// # Arguments
    ///
    /// * `robot_name` - Name of the robot
    /// * `env_id` - Environment ID
    /// * `headless` - Run without UI
    pub fn run(&self, robot_name: &str, env_id: &str, headless: bool) -> Result<()> {
        // Validate Godot is available
        let godot_info = self.validate_godot()?;

        // Determine Godot project path
        let godot_project_path = if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
            // Framework dev mode - use monorepo path
            PathBuf::from(framework_path).join(paths::framework::SIMULATION_GODOT_DIR)
        } else {
            // Generated project mode - use relative path
            PathBuf::from(paths::project::SIMULATION_GODOT_DIR)
        };

        let mut cmd = Command::new(&godot_info.path);
        cmd.arg("--path").arg(godot_project_path);

        if headless {
            cmd.arg("--headless");
        }

        cmd.arg("--")
            .arg(format!("--env={}", env_id))
            .arg(format!("--robot={}", robot_name));

        // Read config paths from mecha10.json if it exists
        let mecha10_config_path = PathBuf::from(paths::PROJECT_CONFIG);
        if mecha10_config_path.exists() {
            if let Ok(config_content) = std::fs::read_to_string(&mecha10_config_path) {
                if let Ok(config) = serde_json::from_str::<crate::types::ProjectConfig>(&config_content) {
                    if let Some(sim_config) = config.simulation {
                        // Pass model config path if specified
                        if let Some(model_config) = sim_config.model_config {
                            let model_config_path = PathBuf::from(&model_config);
                            if model_config_path.exists() {
                                cmd.arg(format!("--model-config={}", model_config_path.display()));
                            }
                        }

                        // Pass environment config path if specified
                        if let Some(env_config) = sim_config.environment_config {
                            let env_config_path = PathBuf::from(&env_config);
                            if env_config_path.exists() {
                                cmd.arg(format!("--env-config={}", env_config_path.display()));
                            }
                        }
                    }
                }
            }
        } else {
            // Framework dev mode or no mecha10.json - use path resolution
            // Use the resolve functions to ensure proper priority (project > framework)
            if let Ok(env_path) = self.resolve_environment_path(env_id) {
                let env_config_path = env_path.join("environment.json");
                if env_config_path.exists() {
                    cmd.arg(format!("--env-config={}", env_config_path.display()));
                }
            }

            if let Ok(model_path) = self.resolve_model_path(robot_name) {
                let model_config_path = model_path.join("model.json");
                if model_config_path.exists() {
                    cmd.arg(format!("--model-config={}", model_config_path.display()));
                }
            }
        }

        let status = cmd.status().context("Failed to launch Godot")?;

        if !status.success() {
            return Err(anyhow::anyhow!("Godot exited with error code: {:?}", status.code()));
        }

        Ok(())
    }

    /// Check if simulation is properly set up
    pub fn is_setup(&self) -> bool {
        self.base_path.exists() && self.base_path.join("robot.tscn").exists()
    }

    /// Get the base path for simulations
    pub fn base_path(&self) -> &Path {
        &self.base_path
    }

    /// Resolve a model path from package namespace or plain name
    ///
    /// Supports multiple formats:
    /// - Package namespace: "@mecha10/simulation-models/rover"
    /// - Plain name: "rover"
    ///
    /// Resolution order:
    /// 1. Framework (monorepo): packages/simulation/models/{name}/
    /// 2. Project-local: simulation/models/{name}/
    ///
    /// # Arguments
    ///
    /// * `model_ref` - Model reference (package namespace or plain name)
    ///
    /// # Returns
    ///
    /// Absolute path to the model directory, or error if not found
    pub fn resolve_model_path(&self, model_ref: &str) -> Result<PathBuf> {
        // Strip package namespace if present
        let model_name = model_ref
            .strip_prefix("@mecha10/simulation-models/")
            .unwrap_or(model_ref);

        // Try project-local path FIRST (project customizations take precedence)
        let project_model_path = PathBuf::from(paths::project::SIMULATION_MODELS_DIR).join(model_name);
        if project_model_path.exists() {
            return Ok(project_model_path);
        }

        // Try MECHA10_FRAMEWORK_PATH environment variable
        if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
            let framework_model_path = PathBuf::from(framework_path)
                .join(paths::framework::SIMULATION_MODELS_DIR)
                .join(model_name);
            if framework_model_path.exists() {
                return Ok(framework_model_path);
            }
        }

        // Try compile-time path from CLI package (packages/cli -> packages/simulation)
        let cli_manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        if let Some(packages_dir) = cli_manifest_dir.parent() {
            let bundled_model_path = packages_dir.join("simulation/models").join(model_name);
            if bundled_model_path.exists() {
                return Ok(bundled_model_path);
            }
        }

        // Try cached assets from GitHub releases (~/.mecha10/simulation/current/models/)
        let assets_service = crate::services::SimulationAssetsService::new();
        if let Some(models_path) = assets_service.models_path() {
            let cached_model_path = models_path.join(model_name);
            if cached_model_path.exists() {
                return Ok(cached_model_path);
            }
        }

        Err(anyhow::anyhow!(
            "Model not found: {}\n\nChecked:\n  • Project: simulation/models/{}\n  • Framework: packages/simulation/models/{}\n  • Cached: ~/.mecha10/simulation/current/models/{}",
            model_ref,
            model_name,
            model_name,
            model_name
        ))
    }

    /// Resolve an environment path from package namespace or plain name
    ///
    /// Supports multiple formats:
    /// - Package namespace: "@mecha10/simulation-environments/basic_arena"
    /// - Plain name: "basic_arena"
    ///
    /// Resolution order:
    /// 1. Project-local: simulation/environments/{name}/ (project customizations take precedence)
    /// 2. Framework (monorepo): packages/simulation/environments/{name}/
    ///
    /// # Arguments
    ///
    /// * `env_ref` - Environment reference (package namespace or plain name)
    ///
    /// # Returns
    ///
    /// Absolute path to the environment directory, or error if not found
    pub fn resolve_environment_path(&self, env_ref: &str) -> Result<PathBuf> {
        // Strip package namespace if present
        let env_name = env_ref
            .strip_prefix("@mecha10/simulation-environments/")
            .unwrap_or(env_ref);

        // Try project-local path FIRST (project customizations take precedence)
        let project_env_path = PathBuf::from(paths::project::SIMULATION_ENVIRONMENTS_DIR).join(env_name);
        if project_env_path.exists() {
            return Ok(project_env_path);
        }

        // Try MECHA10_FRAMEWORK_PATH environment variable
        if let Ok(framework_path) = std::env::var("MECHA10_FRAMEWORK_PATH") {
            let framework_env_path = PathBuf::from(framework_path)
                .join(paths::framework::SIMULATION_ENVIRONMENTS_DIR)
                .join(env_name);
            if framework_env_path.exists() {
                return Ok(framework_env_path);
            }
        }

        // Try compile-time path from CLI package (packages/cli -> packages/simulation)
        let cli_manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        if let Some(packages_dir) = cli_manifest_dir.parent() {
            let bundled_env_path = packages_dir.join("simulation/environments").join(env_name);
            if bundled_env_path.exists() {
                return Ok(bundled_env_path);
            }
        }

        // Try cached assets from GitHub releases (~/.mecha10/simulation/current/environments/)
        let assets_service = crate::services::SimulationAssetsService::new();
        if let Some(envs_path) = assets_service.environments_path() {
            let cached_env_path = envs_path.join(env_name);
            if cached_env_path.exists() {
                return Ok(cached_env_path);
            }
        }

        Err(anyhow::anyhow!(
            "Environment not found: {}\n\nChecked:\n  • Project: simulation/environments/{}\n  • Framework: packages/simulation/environments/{}\n  • Cached: ~/.mecha10/simulation/current/environments/{}",
            env_ref,
            env_name,
            env_name,
            env_name
        ))
    }
}

impl Default for SimulationService {
    fn default() -> Self {
        Self::new()
    }
}

/// Information about Godot installation
#[derive(Debug, Clone)]
pub struct GodotInfo {
    pub path: String,
    pub version: String,
    pub in_path: bool,
}

/// Result of simulation generation
#[derive(Debug)]
pub struct GenerationResult {
    pub robot_scene: PathBuf,
    pub environments: Vec<String>,
    pub available_count: usize,
    pub godot_info: GodotInfo,
}

/// Information about an environment
#[derive(Debug, Clone)]
pub struct EnvironmentInfo {
    pub id: String,
    pub name: String,
    pub description: String,
    pub score: i32,
    pub available: bool,
}

/// Result of configuration validation
#[derive(Debug)]
pub struct ValidationResult {
    pub is_valid: bool,
    pub errors: Vec<String>,
    pub warnings: Vec<String>,
    pub platform: String,
    pub sensor_count: usize,
    pub node_count: usize,
}
