//! autopilot binary — runtime composition root.
//!
//! Flow: parse CLI → load config → init tracing → create state dirs → wire
//! actors → start health server → signal systemd ready → await shutdown.

mod bit_runner;
mod health_server;
mod runtime;

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

use anyhow::{Context, Result};
use clap::Parser;
use tokio::signal;
use tracing::info;

use shared::config::{Config, ConfigLoader};
use shared::observability::{self, LogFormat};

#[derive(Debug, Parser)]
#[command(
    name = "autopilot",
    about = "AZAION autopilot — onboard mission executor"
)]
struct Cli {
    /// Path to the TOML config file. Overrides `AUTOPILOT_CONFIG`.
    #[arg(
        long,
        env = "AUTOPILOT_CONFIG",
        default_value = "config/autopilot.dev.toml"
    )]
    config: PathBuf,

    /// Active mission UUID (per-flight; required at flight time, optional in dev).
    #[arg(long, env = "AUTOPILOT_MISSION_ID")]
    mission_id: Option<String>,

    /// Override the configured health-server bind address.
    #[arg(long, env = "AUTOPILOT_HEALTH_BIND")]
    health_bind: Option<String>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();
    let mut config: Config = ConfigLoader::from_path(&cli.config)
        .with_context(|| format!("loading config from {}", cli.config.display()))?;

    if let Some(addr) = cli.health_bind.as_deref() {
        config.health.bind = addr.to_string();
    }

    let log_format = LogFormat::parse(&config.observability.log_format);
    let _ = observability::init(log_format, &config.observability.default_log_filter);

    info!(
        config = %cli.config.display(),
        bind = %config.health.bind,
        mission_id = ?cli.mission_id,
        "autopilot starting"
    );

    runtime::ensure_state_directories(&config.storage.state_dir)
        .with_context(|| format!("preparing state dir {}", config.storage.state_dir))?;

    let bit = bit_runner::run_preflight_bit(&config).await?;
    info!(?bit, "pre-flight BIT outcome");

    let runtime = Arc::new(runtime::Runtime::new(config.clone()));
    let health_handle = health_server::spawn(config.health.bind.clone(), runtime.clone()).await?;

    notify_systemd_ready();

    info!("autopilot is running. Press Ctrl-C to shut down.");
    wait_for_shutdown_signal().await;

    info!("shutdown signal received");
    notify_systemd_stopping();
    health_handle.shutdown().await;
    Ok(())
}

async fn wait_for_shutdown_signal() {
    #[cfg(unix)]
    {
        use signal::unix::{signal as unix_signal, SignalKind};
        let mut sigterm = unix_signal(SignalKind::terminate()).expect("install SIGTERM handler");
        let mut sigint = unix_signal(SignalKind::interrupt()).expect("install SIGINT handler");
        tokio::select! {
            _ = sigterm.recv() => {}
            _ = sigint.recv() => {}
        }
    }
    #[cfg(not(unix))]
    {
        let _ = signal::ctrl_c().await;
    }
}

#[cfg(target_os = "linux")]
fn notify_systemd_ready() {
    if let Err(e) = sd_notify::notify(false, &[sd_notify::NotifyState::Ready]) {
        tracing::warn!(error = %e, "sd_notify READY failed (running outside systemd is fine)");
    }
}

#[cfg(target_os = "linux")]
fn notify_systemd_stopping() {
    if let Err(e) = sd_notify::notify(false, &[sd_notify::NotifyState::Stopping]) {
        tracing::warn!(error = %e, "sd_notify STOPPING failed");
    }
}

#[cfg(not(target_os = "linux"))]
fn notify_systemd_ready() {}

#[cfg(not(target_os = "linux"))]
fn notify_systemd_stopping() {}
