//! Standalone Wasmeld control-plane and data-plane server entry point.
//!
//! Configuration is supplied through environment variables:
//!
//! - `WASMELD_ADDR` binds the management API (default `127.0.0.1:8080`).
//! - `WASMELD_GATEWAY_ADDR` binds the public byte API (default `0.0.0.0:8081`).
//! - `WASMELD_ARTIFACT_DIR`, `WASMELD_WIT_REGISTRY_DIR`, and
//!   `WASMELD_DATABASE_PATH` select durable local storage.
//! - `WASMELD_ALLOWED_ORIGINS` is a comma-separated management UI CORS list.
//! - `RUST_LOG` configures tracing.
//!
//! For example:
//!
//! ```text
//! WASMELD_ADDR=127.0.0.1:8080 \
//! WASMELD_GATEWAY_ADDR=0.0.0.0:8081 \
//! RUST_LOG=wasmeld_console=debug \
//! cargo run -p wasmeld-console
//! ```
//!
//! The defaults expose only the gateway on non-loopback interfaces.
//! Authentication and TLS must be supplied by the deployment environment.

use std::{env, net::SocketAddr, path::PathBuf, sync::Arc};

use axum::http::HeaderValue;
use tracing::info;
use tracing_subscriber::EnvFilter;
use wasmeld_console::{Console, ConsoleConfig, app, gateway_app};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| EnvFilter::new("wasmeld_console=info")),
        )
        .init();

    let management_address = env::var("WASMELD_ADDR")
        .unwrap_or_else(|_| "127.0.0.1:8080".to_owned())
        .parse::<SocketAddr>()?;
    let gateway_address = env::var("WASMELD_GATEWAY_ADDR")
        .unwrap_or_else(|_| "0.0.0.0:8081".to_owned())
        .parse::<SocketAddr>()?;
    let artifact_dir = env::var_os("WASMELD_ARTIFACT_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("var/wasmeld/components"));
    let database_path = env::var_os("WASMELD_DATABASE_PATH")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("var/wasmeld/console.db"));
    let wit_registry_dir = env::var_os("WASMELD_WIT_REGISTRY_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("var/wasmeld/wit-packages"));
    let allowed_origins = allowed_origins()?;
    let console = Arc::new(
        Console::new(ConsoleConfig {
            artifact_dir,
            database_path,
            wit_registry_dir,
            ..ConsoleConfig::default()
        })
        .await?,
    );
    let management = app(Arc::clone(&console), allowed_origins);
    let gateway = gateway_app(Arc::clone(&console));
    let management_listener = tokio::net::TcpListener::bind(management_address).await?;
    let gateway_listener = tokio::net::TcpListener::bind(gateway_address).await?;
    // Both servers share one shutdown edge so neither listener remains alive
    // after Ctrl+C has begun process termination.
    let (shutdown_sender, shutdown_receiver) = tokio::sync::watch::channel(false);
    let signal_task = tokio::spawn(async move {
        shutdown_signal().await;
        let _ = shutdown_sender.send(true);
    });

    info!(%management_address, "Wasmeld management API listening");
    info!(%gateway_address, "Wasmeld Gateway listening");
    let result = tokio::try_join!(
        axum::serve(management_listener, management)
            .with_graceful_shutdown(wait_for_shutdown(shutdown_receiver.clone())),
        axum::serve(gateway_listener, gateway)
            .with_graceful_shutdown(wait_for_shutdown(shutdown_receiver)),
    );
    signal_task.abort();
    result?;
    // Axum has stopped accepting requests at this point. Flush accepted
    // telemetry before dropping Console; persistence failures are traced by
    // the worker because telemetry is an operational, best-effort stream.
    console.flush_invocation_telemetry().await;
    Ok(())
}

fn allowed_origins() -> Result<Vec<HeaderValue>, Box<dyn std::error::Error>> {
    let value = env::var("WASMELD_ALLOWED_ORIGINS")
        .unwrap_or_else(|_| "http://localhost:3000,http://127.0.0.1:3000".to_owned());
    value
        .split(',')
        .filter(|origin| !origin.trim().is_empty())
        .map(|origin| origin.trim().parse::<HeaderValue>().map_err(Into::into))
        .collect()
}

async fn shutdown_signal() {
    if tokio::signal::ctrl_c().await.is_err() {
        tracing::warn!("failed to install Ctrl+C handler");
    }
}

async fn wait_for_shutdown(mut receiver: tokio::sync::watch::Receiver<bool>) {
    while !*receiver.borrow() {
        if receiver.changed().await.is_err() {
            break;
        }
    }
}
