#![allow(dead_code)]

//! CLI execution context
//!
//! The CliContext is the single source of truth for runtime configuration,
//! combining CLI flags, environment variables, and config file settings.

use crate::paths;
use crate::services::{
    ComponentCatalogService, ConfigService, DeploymentService, DevService, DiscoveryService, DockerService,
    InitService, PackageService, ProcessService, ProjectService, ProjectTemplateService, RedisService,
    SimulationService, TemplateDownloadService, TemplateService, TopologyService,
};
use crate::types::ProjectConfig;
use std::path::PathBuf;
use tracing::Level;

/// CLI execution context
///
/// This provides normalized runtime configuration for the CLI, combining:
/// - CLI flags
/// - Environment variables
/// - Config file settings
/// - Computed defaults
///
/// Use `CliContextBuilder` to construct with proper precedence handling.
///
/// # Example
///
/// ```rust,ignore
/// use mecha10_cli::{CliContext, CliContextBuilder};
///
/// let ctx = CliContextBuilder::new()
///     .verbose(true)
///     .build()?;
///
/// assert!(ctx.is_verbose());
/// ```
pub struct CliContext {
    /// Path to the project config file (mecha10.json)
    pub config_path: PathBuf,

    /// Logging level
    pub log_level: Level,

    /// Project working directory
    pub working_dir: PathBuf,

    /// Redis connection URL (from env or config)
    redis_url: String,

    /// PostgreSQL connection URL (from env or config)
    postgres_url: Option<String>,

    /// Verbose output mode
    verbose: bool,

    /// Development mode (affects error display, hot reload, etc.)
    dev_mode: bool,

    /// Cached services (lazy initialization)
    redis_service: Option<RedisService>,
    project_service: Option<ProjectService>,
    template_service: Option<TemplateService>,
    simulation_service: Option<SimulationService>,
    process_service: Option<ProcessService>,
    /// Shared process service for concurrent access (used by CommandListener)
    shared_process_service: Option<std::sync::Arc<std::sync::Mutex<ProcessService>>>,
    docker_service: Option<DockerService>,
    package_service: Option<PackageService>,
    deployment_service: Option<DeploymentService>,
    component_catalog_service: Option<ComponentCatalogService>,
    discovery_service: Option<DiscoveryService>,
    init_service: Option<InitService>,
    project_template_service: Option<ProjectTemplateService>,
    dev_service: Option<DevService>,
    topology_service: Option<TopologyService>,
    template_download_service: Option<TemplateDownloadService>,

    /// Cached project configuration (loaded lazily)
    /// Note: We don't store this as ProjectConfig doesn't implement Clone
    /// Instead, commands should load it when needed
    _phantom: std::marker::PhantomData<ProjectConfig>,
}

impl CliContext {
    /// Load the project configuration
    ///
    /// This loads the configuration from disk each time it's called.
    /// For better performance in commands that access config multiple times,
    /// call this once and store the result.
    pub async fn load_project_config(&self) -> anyhow::Result<ProjectConfig> {
        ConfigService::load_from(&self.config_path).await
    }

    /// Check if a project is initialized (mecha10.json exists)
    pub fn is_project_initialized(&self) -> bool {
        ConfigService::is_initialized(&self.working_dir)
    }

    /// Get a path relative to the project root
    pub fn project_path(&self, path: &str) -> PathBuf {
        self.working_dir.join(path)
    }

    // ========== Convenience Methods ==========

    /// Check if verbose mode is enabled
    pub fn is_verbose(&self) -> bool {
        self.verbose
    }

    /// Check if development mode is enabled
    pub fn is_dev_mode(&self) -> bool {
        self.dev_mode
    }

    /// Get the Redis connection URL
    pub fn redis_url(&self) -> &str {
        &self.redis_url
    }

    /// Get the PostgreSQL connection URL if configured
    pub fn postgres_url(&self) -> Option<&str> {
        self.postgres_url.as_deref()
    }

    // ========== Service Accessors ==========

    /// Get Redis service (creates on first access)
    pub fn redis(&mut self) -> anyhow::Result<&RedisService> {
        if self.redis_service.is_none() {
            self.redis_service = Some(RedisService::new(&self.redis_url)?);
        }
        Ok(self.redis_service.as_ref().unwrap())
    }

    /// Get Project service (creates on first access)
    pub fn project(&mut self) -> anyhow::Result<&ProjectService> {
        if self.project_service.is_none() {
            self.project_service = Some(ProjectService::detect(&self.working_dir)?);
        }
        Ok(self.project_service.as_ref().unwrap())
    }

    /// Get Template service (creates on first access)
    pub fn template(&mut self) -> &TemplateService {
        if self.template_service.is_none() {
            self.template_service = Some(TemplateService::new());
        }
        self.template_service.as_ref().unwrap()
    }

    /// Get Simulation service (creates on first access)
    pub fn simulation(&mut self) -> &SimulationService {
        if self.simulation_service.is_none() {
            self.simulation_service = Some(SimulationService::new());
        }
        self.simulation_service.as_ref().unwrap()
    }

    /// Get Process service (creates on first access)
    pub fn process(&mut self) -> &mut ProcessService {
        if self.process_service.is_none() {
            self.process_service = Some(ProcessService::new());
        }
        self.process_service.as_mut().unwrap()
    }

    /// Get shared Process service for concurrent access
    ///
    /// This returns an Arc<Mutex<ProcessService>> that can be safely shared
    /// between tasks (e.g., DevSession and CommandListener).
    /// Uses std::sync::Mutex so it works in both sync and async contexts.
    pub fn shared_process(&mut self) -> std::sync::Arc<std::sync::Mutex<ProcessService>> {
        if self.shared_process_service.is_none() {
            self.shared_process_service = Some(std::sync::Arc::new(std::sync::Mutex::new(ProcessService::new())));
        }
        self.shared_process_service.as_ref().unwrap().clone()
    }

    /// Get Docker service (creates on first access)
    pub fn docker(&mut self) -> &DockerService {
        if self.docker_service.is_none() {
            self.docker_service = Some(DockerService::new());
        }
        self.docker_service.as_ref().unwrap()
    }

    /// Get Package service (creates on first access)
    ///
    /// Note: This requires a valid project to be detected with name and version.
    pub fn package(&mut self) -> anyhow::Result<&PackageService> {
        if self.package_service.is_none() {
            let project = self.project()?;
            let name = project.name()?;
            let version = project.version()?;
            self.package_service = Some(PackageService::new(name, version, self.working_dir.clone())?);
        }
        Ok(self.package_service.as_ref().unwrap())
    }

    /// Get Deployment service (creates on first access)
    pub fn deployment(&mut self) -> &DeploymentService {
        if self.deployment_service.is_none() {
            self.deployment_service = Some(DeploymentService::new());
        }
        self.deployment_service.as_ref().unwrap()
    }

    /// Get Component Catalog service (creates on first access)
    pub fn component_catalog(&mut self) -> &ComponentCatalogService {
        if self.component_catalog_service.is_none() {
            self.component_catalog_service = Some(ComponentCatalogService::new());
        }
        self.component_catalog_service.as_ref().unwrap()
    }

    /// Get Discovery service (creates on first access)
    pub fn discovery(&mut self) -> &mut DiscoveryService {
        if self.discovery_service.is_none() {
            self.discovery_service = Some(DiscoveryService::new());
        }
        self.discovery_service.as_mut().unwrap()
    }

    /// Get Init service (creates on first access)
    pub fn init_service(&mut self) -> &InitService {
        if self.init_service.is_none() {
            self.init_service = Some(InitService::new());
        }
        self.init_service.as_ref().unwrap()
    }

    /// Get Project Template service (creates on first access)
    pub fn project_template_service(&mut self) -> &ProjectTemplateService {
        if self.project_template_service.is_none() {
            self.project_template_service = Some(ProjectTemplateService::new());
        }
        self.project_template_service.as_ref().unwrap()
    }

    /// Get Dev service (creates on first access)
    pub fn dev(&mut self) -> &DevService {
        if self.dev_service.is_none() {
            self.dev_service = Some(DevService::new(self.redis_url.clone()));
        }
        self.dev_service.as_ref().unwrap()
    }

    /// Get Topology service (creates on first access)
    ///
    /// The topology service performs static analysis of project structure,
    /// extracting node definitions and pub/sub topic relationships from source code.
    pub fn topology(&mut self) -> &TopologyService {
        if self.topology_service.is_none() {
            self.topology_service = Some(TopologyService::new(self.working_dir.clone()));
        }
        self.topology_service.as_ref().unwrap()
    }

    /// Get Template Download service (creates on first access)
    ///
    /// Downloads project templates from GitHub releases on-demand.
    /// Templates are versioned and cached locally for offline use.
    pub fn template_download(&mut self) -> &TemplateDownloadService {
        if self.template_download_service.is_none() {
            self.template_download_service = Some(TemplateDownloadService::new());
        }
        self.template_download_service.as_ref().unwrap()
    }

    // ========== Validation Methods ==========

    /// Validate that Docker is installed and running
    pub fn validate_docker(&mut self) -> anyhow::Result<()> {
        let docker = self.docker();
        docker.check_installation()?;
        docker.check_daemon()?;
        Ok(())
    }

    /// Validate that Redis is accessible
    pub async fn validate_redis(&mut self) -> anyhow::Result<()> {
        let redis = self.redis()?;
        // Try to connect by performing a simple operation
        // The service will fail to create if connection fails
        let _ = redis;
        Ok(())
    }

    /// Validate project structure
    ///
    /// Checks that the project has the expected directory structure
    /// and required files.
    pub fn validate_project_structure(&self) -> anyhow::Result<()> {
        if !self.is_project_initialized() {
            return Err(anyhow::anyhow!("Project not initialized. Run 'mecha10 init' first."));
        }

        // Check required directories
        let required_dirs = vec!["nodes", "drivers"];
        for dir in required_dirs {
            let path = self.project_path(dir);
            if !path.exists() {
                return Err(anyhow::anyhow!(
                    "Required directory missing: {}. Expected at: {}",
                    dir,
                    path.display()
                ));
            }
        }

        Ok(())
    }

    /// Validate that Godot is installed (for simulation)
    pub fn validate_godot(&mut self) -> anyhow::Result<()> {
        let sim = self.simulation();
        sim.validate_godot()?;
        Ok(())
    }

    // ========== Environment Helpers ==========

    /// Check if running in CI environment
    pub fn is_ci(&self) -> bool {
        std::env::var("CI").is_ok()
            || std::env::var("GITHUB_ACTIONS").is_ok()
            || std::env::var("GITLAB_CI").is_ok()
            || std::env::var("CIRCLECI").is_ok()
            || std::env::var("JENKINS_HOME").is_ok()
    }

    /// Check if running in interactive terminal
    pub fn is_interactive(&self) -> bool {
        atty::is(atty::Stream::Stdout) && !self.is_ci()
    }

    /// Get logs directory path
    pub fn logs_dir(&self) -> PathBuf {
        self.project_path("logs")
    }

    /// Get data directory path
    pub fn data_dir(&self) -> PathBuf {
        self.project_path("data")
    }

    /// Get recordings directory path
    pub fn recordings_dir(&self) -> PathBuf {
        self.project_path("data/recordings")
    }

    /// Get maps directory path
    pub fn maps_dir(&self) -> PathBuf {
        self.project_path("data/maps")
    }

    /// Get telemetry directory path
    pub fn telemetry_dir(&self) -> PathBuf {
        self.project_path("data/telemetry")
    }

    /// Get simulation directory path
    pub fn simulation_dir(&self) -> PathBuf {
        self.project_path("simulation")
    }

    /// Get target/debug directory path
    pub fn target_debug_dir(&self) -> PathBuf {
        self.project_path("target/debug")
    }

    /// Get target/release directory path
    pub fn target_release_dir(&self) -> PathBuf {
        self.project_path("target/release")
    }

    /// Get packages output directory path
    pub fn packages_dir(&self) -> PathBuf {
        self.project_path("target/packages")
    }

    /// Ensure a directory exists, creating it if necessary
    pub fn ensure_dir(&self, path: &PathBuf) -> anyhow::Result<()> {
        if !path.exists() {
            std::fs::create_dir_all(path)?;
        }
        Ok(())
    }
}

impl Default for CliContext {
    fn default() -> Self {
        CliContextBuilder::new()
            .log_level(Level::INFO)
            .build()
            .expect("Failed to build default CliContext")
    }
}

// ========== Builder Pattern ==========

/// Builder for CliContext with proper precedence handling
///
/// Precedence order (highest to lowest):
/// 1. Explicitly set builder values
/// 2. Environment variables
/// 3. Config file values (if project initialized)
/// 4. Defaults
///
/// # Example
///
/// ```rust,ignore
/// use mecha10_cli::CliContextBuilder;
/// use tracing::Level;
///
/// let ctx = CliContextBuilder::new()
///     .config_path(Some("custom/mecha10.json".into()))
///     .log_level(Level::DEBUG)
///     .verbose(true)
///     .dev_mode(true)
///     .build()?;
/// ```
pub struct CliContextBuilder {
    config_path: Option<PathBuf>,
    log_level: Option<Level>,
    working_dir: Option<PathBuf>,
    redis_url: Option<String>,
    postgres_url: Option<String>,
    verbose: Option<bool>,
    dev_mode: Option<bool>,
}

impl CliContextBuilder {
    /// Create a new builder
    pub fn new() -> Self {
        Self {
            config_path: None,
            log_level: None,
            working_dir: None,
            redis_url: None,
            postgres_url: None,
            verbose: None,
            dev_mode: None,
        }
    }

    /// Set config file path
    pub fn config_path(mut self, path: Option<PathBuf>) -> Self {
        self.config_path = path;
        self
    }

    /// Set log level
    pub fn log_level(mut self, level: Level) -> Self {
        self.log_level = Some(level);
        self
    }

    /// Set working directory
    pub fn working_dir(mut self, dir: PathBuf) -> Self {
        self.working_dir = Some(dir);
        self
    }

    /// Set Redis URL
    pub fn redis_url(mut self, url: String) -> Self {
        self.redis_url = Some(url);
        self
    }

    /// Set PostgreSQL URL
    pub fn postgres_url(mut self, url: Option<String>) -> Self {
        self.postgres_url = url;
        self
    }

    /// Set verbose mode
    pub fn verbose(mut self, verbose: bool) -> Self {
        self.verbose = Some(verbose);
        self
    }

    /// Set development mode
    pub fn dev_mode(mut self, dev: bool) -> Self {
        self.dev_mode = Some(dev);
        self
    }

    /// Build the CliContext with proper precedence
    pub fn build(self) -> anyhow::Result<CliContext> {
        // Working directory: explicit > current dir
        let working_dir = self
            .working_dir
            .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));

        // Config path: explicit > working_dir/mecha10.json
        let config_path = self.config_path.unwrap_or_else(|| working_dir.join(paths::PROJECT_CONFIG));

        // Try to load Redis URL from config file (if it exists)
        let config_redis_url = if config_path.exists() {
            std::fs::read_to_string(&config_path)
                .ok()
                .and_then(|content| serde_json::from_str::<serde_json::Value>(&content).ok())
                .and_then(|json| json.get("redis")?.get("url")?.as_str().map(String::from))
        } else {
            None
        };

        // Redis URL: explicit > env > config file > default
        let redis_url = self
            .redis_url
            .or_else(|| std::env::var("MECHA10_REDIS_URL").ok())
            .or_else(|| std::env::var("REDIS_URL").ok())
            .or(config_redis_url)
            .unwrap_or_else(|| "redis://localhost:6380".to_string());

        // Postgres URL: explicit > env
        let postgres_url = self
            .postgres_url
            .or_else(|| std::env::var("DATABASE_URL").ok())
            .or_else(|| std::env::var("POSTGRES_URL").ok());

        // Log level: explicit > INFO
        let log_level = self.log_level.unwrap_or(Level::INFO);

        // Verbose: explicit > false
        let verbose = self.verbose.unwrap_or(false);

        // Dev mode: explicit > env > false
        let dev_mode = self.dev_mode.unwrap_or_else(|| {
            std::env::var("MECHA10_DEV_MODE")
                .or_else(|_| std::env::var("DEV_MODE"))
                .map(|v| v == "1" || v.to_lowercase() == "true")
                .unwrap_or(false)
        });

        Ok(CliContext {
            config_path,
            log_level,
            working_dir,
            redis_url,
            postgres_url,
            verbose,
            dev_mode,
            redis_service: None,
            project_service: None,
            template_service: None,
            simulation_service: None,
            process_service: None,
            shared_process_service: None,
            docker_service: None,
            package_service: None,
            deployment_service: None,
            component_catalog_service: None,
            discovery_service: None,
            init_service: None,
            project_template_service: None,
            dev_service: None,
            topology_service: None,
            template_download_service: None,
            _phantom: std::marker::PhantomData,
        })
    }
}

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