use std::sync::Arc;

use anyhow::{Context, Result, bail};
use soflare::AppState;
use soflare::config::Config;
use soflare::metrics;
use soflare::pushkin::PushkinRegistry;
use soflare::service::build_router_with_access_log;
use salvo::prelude::*;
use tokio::task::JoinSet;
use tracing_subscriber::EnvFilter;

#[tokio::main]
async fn main() -> Result<()> {
    init_tracing();

    let (config, path) = Config::load()?;
    tracing::info!(config = %path.display(), "using configuration file");
    config.emit_startup_warnings();
    metrics::init();

    let registry = Arc::new(PushkinRegistry::from_config(&config).await?);
    if registry.is_empty() {
        bail!("no app IDs are configured; define at least one entry under apps");
    }

    let state = Arc::new(AppState::new(registry));
    let router = Arc::new(build_router_with_access_log(state, &config.log.access));

    let mut servers = JoinSet::new();
    for bind in config.http.listen_addrs()? {
        let router = router.clone();
        servers.spawn(async move {
            let acceptor = TcpListener::new(bind.clone()).bind().await;
            tracing::info!(listen = %bind, "starting server");
            Server::new(acceptor).serve(router).await;
            Result::<()>::Ok(())
        });
    }
    if config.metrics.prometheus.enabled {
        let bind = config.metrics.prometheus.listen_addr();
        let router = metrics::build_router();
        servers.spawn(async move {
            let acceptor = TcpListener::new(bind.clone()).bind().await;
            tracing::info!(listen = %bind, "starting prometheus metrics server");
            Server::new(acceptor).serve(router).await;
            Result::<()>::Ok(())
        });
    }

    while let Some(result) = servers.join_next().await {
        result.context("server task join failure")??;
    }

    Ok(())
}

fn init_tracing() {
    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
    tracing_subscriber::fmt()
        .with_env_filter(env_filter)
        .with_target(false)
        .compact()
        .init();
}
