//! Provisioning Platform Daemon
//!
//! A thin wrapper around daemon-cli library with provisioning-specific
//! configuration. All core functionality (Nushell execution, config rendering,
//! i18n) is provided by daemon-cli.

mod config;
mod nats_subscriber;

use std::path::PathBuf;
use std::sync::Arc;

use axum::Router;
use clap::Parser;
use daemon_cli::{
    api::api_routes,
    core::{DaemonConfig, HierarchicalCache, Result},
    nushell::{CommandExecutor, NushellEnvironment},
    orchestration::{
        AuditOperation, EncryptOperation, InitServsOperation, OperationRegistry, RuntimeOperation,
        ValidaOperation,
    },
    AppState,
};
use tokio::net::TcpListener;

/// Provisioning daemon - Nushell execution and configuration rendering service
#[derive(Parser, Debug)]
#[command(name = "provisioning-nu-daemon")]
#[command(about = "Provisioning Nushell execution daemon (persistent script runner)")]
#[command(version = env!("CARGO_PKG_VERSION"))]
struct Args {
    /// Configuration file path (highest priority)
    #[arg(short = 'c', long, env = "PROVISIONING_DAEMON_CONFIG")]
    config: Option<PathBuf>,

    /// Configuration directory (searches for provisioning-daemon.ncl|toml|json)
    #[arg(long, env = "PROVISIONING_CONFIG_DIR")]
    config_dir: Option<PathBuf>,

    /// Deployment mode (solo, multiuser, cicd, enterprise)
    #[arg(short = 'm', long, env = "PROVISIONING_DAEMON_MODE")]
    mode: Option<String>,

    /// Enable verbose logging
    #[arg(short = 'v', long)]
    verbose: bool,

    /// Validate configuration and exit
    #[arg(long)]
    validate_config: bool,

    /// Show configuration and exit
    #[arg(long)]
    show_config: bool,
}

#[tokio::main]
async fn main() -> Result<()> {
    let args = Args::parse();

    // Initialize centralized observability (logging, metrics, health checks)
    let _guard = observability::init_from_env("daemon", env!("CARGO_PKG_VERSION"))
        .map_err(|e| daemon_cli::core::DaemonError::http_server_error(e.to_string()))?;

    // Check if daemon is enabled in deployment-mode.ncl
    if let Ok(deployment) = platform_config::load_deployment_mode() {
        if let Ok(enabled) = deployment.is_service_enabled("provisioning_daemon") {
            if !enabled {
                tracing::warn!("⚠ Provisioning Daemon is DISABLED in deployment-mode.ncl");
                std::process::exit(1);
            }
            tracing::info!("✓ Provisioning Daemon is ENABLED in deployment-mode.ncl");
        }
    }

    tracing::info!(
        "Starting Provisioning Daemon v{}",
        env!("CARGO_PKG_VERSION")
    );

    // Load provisioning-daemon configuration from Nickel via platform-config
    tracing::info!("Loading provisioning-daemon configuration from provisioning-daemon.ncl");
    let provisioning_config = match config::ProvisioningDaemonConfig::load() {
        Ok(cfg) => {
            let home = std::env::var("HOME").unwrap_or_else(|_| "~".to_string());
            #[cfg(target_os = "macos")]
            let config_path = format!(
                "{}/Library/Application \
                 Support/provisioning/platform/config/provisioning-daemon.ncl",
                home
            );
            #[cfg(not(target_os = "macos"))]
            let config_path = format!(
                "{}/.config/provisioning/platform/config/provisioning-daemon.ncl",
                home
            );

            tracing::info!("✓ Loaded configuration from: {}", config_path);
            tracing::info!("  Server: {}:{}", cfg.server.host, cfg.server.port);
            tracing::info!(
                "  Daemon: enabled={}, poll_interval={}s, max_workers={}",
                cfg.daemon.enabled,
                cfg.daemon.poll_interval,
                cfg.daemon.max_workers
            );
            cfg
        }
        Err(e) => {
            tracing::error!("✗ Failed to load provisioning-daemon.ncl: {}", e);
            std::process::exit(1);
        }
    };

    // Load daemon-cli configuration from TOML (if provided) or use defaults
    // The daemon-cli library needs its own infrastructure config separate from the
    // service config
    let mut config = if let Some(explicit_path) = args.config {
        // Use explicit path from CLI argument
        DaemonConfig::load(Some(explicit_path))?
    } else {
        // Load from config_dir if provided, otherwise use default
        if let Some(config_dir) = args.config_dir {
            let config_path = config_dir.join("provisioning-daemon-cli.toml");
            if config_path.exists() {
                DaemonConfig::load(Some(config_path))?
            } else {
                // Use defaults if no daemon-cli config found
                DaemonConfig::default()
            }
        } else {
            DaemonConfig::default()
        }
    };

    // Override the daemon-cli bind port with the port from provisioning-daemon.ncl
    // This ensures consistency: the service config (provisioning-daemon.ncl)
    // controls the port
    config.server.bind = format!(
        "{}:{}",
        provisioning_config.server.host, provisioning_config.server.port
    );

    // Handle special commands
    if args.validate_config {
        config.validate()?;
        println!("✓ Configuration is valid");
        return Ok(());
    }

    if args.show_config {
        config.print();
        return Ok(());
    }

    // Validate configuration
    config.validate()?;
    tracing::info!("✓ Daemon-cli configuration validated");
    tracing::info!(
        "  Bind address: {} (from provisioning-daemon.ncl)",
        config.server.bind
    );
    tracing::info!("  Executor: {}", config.server.executor_strategy);

    // Create cache system
    let cache = HierarchicalCache::new()?;
    let cache_arc = Arc::new(cache);

    // Initialize Nushell environment so /api/v1/execute uses the same runtime
    // path as provisioning-daemon-cli.
    let nushell_env = match initialize_nushell_environment().await {
        Ok(env) => {
            tracing::info!("✓ Nushell environment initialized successfully");
            Some(env)
        }
        Err(e) => {
            tracing::warn!("Failed to initialize Nushell environment: {}", e);
            tracing::warn!("Continuing without Nushell support");
            None
        }
    };

    let nushell_executor = if let Some(env) = nushell_env {
        match create_nushell_executor(env).await {
            Ok(executor) => {
                tracing::info!("✓ Nushell command executor ready");
                Some(Arc::new(executor))
            }
            Err(e) => {
                tracing::warn!("Failed to create Nushell executor: {}", e);
                None
            }
        }
    } else {
        None
    };

    // Create operation registry
    let mut registry = OperationRegistry::default();
    registry.register("valida", Arc::new(ValidaOperation::new(cache_arc.clone())));
    registry.register(
        "runtime",
        Arc::new(RuntimeOperation::new(cache_arc.clone())),
    );
    registry.register(
        "encrypt",
        Arc::new(EncryptOperation::new(cache_arc.clone())),
    );
    registry.register(
        "init-servs",
        Arc::new(InitServsOperation::new(cache_arc.clone())),
    );
    registry.register("audit", Arc::new(AuditOperation::new(cache_arc.clone())));

    tracing::info!(
        "✓ Registered {} operations: {}",
        registry.len(),
        registry.list().join(", ")
    );

    // Create app state with Nushell executor when available.
    let state = if let Some(executor) = nushell_executor {
        AppState::with_nushell(config.clone(), cache_arc, registry, Some(executor))
    } else {
        AppState::new(config.clone(), cache_arc, registry)
    };

    // Create router
    let app = Router::new().nest("/api/v1", api_routes(state.clone()));

    // Spawn NATS component lifecycle subscriber (best-effort: NATS may be absent in solo mode)
    let prov_root_for_nats = resolve_provisioning_root()
        .unwrap_or_else(|_| PathBuf::from("/usr/local/provisioning"));
    tokio::spawn(nats_subscriber::run(prov_root_for_nats));

    // Start server using the configured bind address
    let addr = config.bind_addr()?;
    let listener = TcpListener::bind(addr).await?;

    tracing::info!("✓ Provisioning daemon listening on http://{}", addr);
    tracing::info!("  API documentation: http://{}/api/v1/health", addr);
    tracing::info!("  Config rendering: http://{}/api/v1/render", addr);
    tracing::info!("  i18n translation: http://{}/api/v1/translate", addr);

    // Run server
    axum::serve(listener, app).await?;

    Ok(())
}

async fn initialize_nushell_environment() -> Result<Arc<NushellEnvironment>> {
    let nu_path = find_nushell_binary()?;
    let provisioning_path = resolve_provisioning_root()?;

    tracing::debug!("Nushell binary: {}", nu_path.display());
    tracing::debug!("Provisioning path: {}", provisioning_path.display());

    let env = Arc::new(NushellEnvironment::new(nu_path, provisioning_path));
    env.load().await?;
    Ok(env)
}

fn resolve_provisioning_root() -> Result<PathBuf> {
    if let Ok(explicit) = std::env::var("PROVISIONING") {
        let path = PathBuf::from(explicit);
        if path.join("core").join("cli").join("provisioning").exists() {
            return Ok(path);
        }
    }

    let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let repo_provisioning = manifest_dir
        .ancestors()
        .nth(3)
        .map(PathBuf::from)
        .ok_or_else(|| daemon_cli::core::DaemonError::nushell_process_error("Failed to resolve provisioning root"))?;

    if repo_provisioning
        .join("core")
        .join("cli")
        .join("provisioning")
        .exists()
    {
        Ok(repo_provisioning)
    } else {
        Err(daemon_cli::core::DaemonError::nushell_process_error(
            "Provisioning root not found from CARGO_MANIFEST_DIR",
        ))
    }
}

fn find_nushell_binary() -> Result<PathBuf> {
    let candidates = ["/usr/local/bin/nu", "/usr/bin/nu", "/opt/homebrew/bin/nu"];

    for candidate in &candidates {
        let path = PathBuf::from(candidate);
        if path.exists() {
            return Ok(path);
        }
    }

    if let Ok(output) = std::process::Command::new("which").arg("nu").output() {
        if output.status.success() {
            let path_str = String::from_utf8(output.stdout)
                .ok()
                .map(|s| s.trim().to_string())
                .unwrap_or_default();
            if !path_str.is_empty() {
                return Ok(PathBuf::from(path_str));
            }
        }
    }

    Err(daemon_cli::core::DaemonError::nushell_process_error(
        "Nushell binary not found in standard locations",
    ))
}

async fn create_nushell_executor(env: Arc<NushellEnvironment>) -> Result<CommandExecutor> {
    let executor = CommandExecutor::new(env);

    if !executor.is_environment_healthy().await {
        tracing::warn!("Nushell environment is not healthy");
    }

    Ok(executor)
}
