use std::sync::Arc;

use anyhow::{Context, Result};
use clap::Parser;
use platform_config::{load_deployment_mode, PlatformStartup};
use provisioning_orchestrator::{config::OrchestratorConfig, AppState, Args};
use tracing::{info, warn};

mod api_catalog;
mod handlers;
mod router;
mod solo_nats;
mod task_executor;

#[tokio::main]
async fn main() -> Result<()> {
    // Parse CLI arguments FIRST (so --help works before any other processing)
    let args = Args::parse();

    if args.dump_api_catalog {
        println!("{}", ontoref_ontology::api::dump_catalog_json());
        return Ok(());
    }

    // Initialize centralized observability (logging, metrics, health checks)
    let _guard = observability::init_from_env("orchestrator", env!("CARGO_PKG_VERSION"))
        .context("Failed to initialize observability")?;

    // Initialize platform startup manager
    let deployment = load_deployment_mode().context("Failed to load deployment-mode.ncl")?;

    // Check if orchestrator is enabled
    if !deployment.is_service_enabled("orchestrator")? {
        warn!("⚠ Orchestrator is DISABLED in deployment-mode.ncl");
        std::process::exit(1);
    }
    info!("✓ Orchestrator is ENABLED in deployment-mode.ncl");

    // Validate dependencies
    let startup = PlatformStartup::new(&deployment.config)
        .context("Failed to initialize platform startup")?;
    startup
        .validate_dependencies("orchestrator")
        .context("Failed to validate orchestrator dependencies")?;

    // Setup Git repositories
    let (_schemas_path, _configs_path) = startup
        .setup_git_repos()
        .context("Failed to setup Git repositories")?;

    // Load orchestrator configuration from Nickel with env + CLI overrides
    // All overrides are validated by Nickel contracts before deserialization
    let config = OrchestratorConfig::load_with_cli_args(&args)
        .context("Failed to load orchestrator configuration")?;

    let port = config.orchestrator.server.port;

    info!(
        "🔧 Loaded orchestrator configuration from NCL, binding to port {}",
        port
    );

    // Solo mode: spawn embedded NATS server before connecting as client
    // The child is kept alive for the entire lifetime of main()
    #[cfg(feature = "nats")]
    let _solo_nats_child = if args.mode.as_deref() == Some("solo") {
        info!("Solo mode: starting embedded NATS server");
        Some(solo_nats::inner::spawn_nats_server(&args.data_dir).await?)
    } else {
        None
    };

    let state = Arc::new(AppState::new(args).await?);

    // Start audit collector (NATS → SurrealDB)
    #[cfg(feature = "nats")]
    {
        use provisioning_orchestrator::audit::collector::run_audit_collector;
        let nats = Arc::clone(&state.nats);
        let db = Arc::clone(&state.db);
        tokio::spawn(async move {
            run_audit_collector(nats, db).await;
        });
        info!("✓ NATS audit collector started");
    }

    // Start InfraState service (formula events + dag_run creation + reconcile)
    #[cfg(feature = "nats")]
    {
        use provisioning_orchestrator::infra_state::InfraStateService;
        let nats = Arc::clone(&state.nats);
        let pool = (*state.db).clone();
        let token = std::env::var("HETZNER_TOKEN")
            .or_else(|_| std::env::var("HCLOUD_TOKEN"))
            .unwrap_or_default();
        let svc = Arc::new(InfraStateService::new(pool, nats, token));
        tokio::spawn(async move {
            if let Err(e) = svc.serve().await {
                error!("InfraStateService exited: {e}");
            }
        });
        info!("✓ InfraState service started");
    }

    // Start task processor
    let processor_state = state.clone();
    tokio::spawn(async move {
        task_executor::process_tasks(processor_state).await;
    });

    let app = router::build_router(state);

    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
        .await
        .context("Failed to bind to address")?;

    info!("Orchestrator listening on port {}", port);

    axum::serve(listener, app).await.context("Server error")?;

    Ok(())
}
