//! Binary entry point. Parses CLI arguments, initialises global configuration,
//! builds the CSS theme, constructs the axum router, and starts the HTTP server.

mod cli;

use clap::Parser;
use teak::config;
use teak::theme::{BASE_CSS, MARKDOWN_CSS};
use teak::{error, routes};
use tower_http::trace::TraceLayer;
use tracing_subscriber::EnvFilter;

async fn shutdown_signal() {
    let ctrl_c = tokio::signal::ctrl_c();
    #[cfg(unix)]
    let sigterm = async {
        tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
            .expect("install SIGTERM handler")
            .recv()
            .await;
    };
    #[cfg(not(unix))]
    let sigterm = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = sigterm => {},
    }
    tracing::info!("shutting down");
}

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

    let args = cli::Args::parse();

    config::set_hostname(args.hostname);
    config::set_ssh_prefix(args.ssh_prefix);
    config::set_base_url(args.base_url);

    let (favicon_data, favicon_mime) = match args.favicon {
        Some(path) => match std::fs::read(&path) {
            Ok(data) => {
                let mime = match path.extension().and_then(|e| e.to_str()) {
                    Some("ico") => "image/x-icon",
                    Some("png") => "image/png",
                    Some("gif") => "image/gif",
                    _ => "image/svg+xml",
                };
                (data, mime)
            }
            Err(e) => {
                tracing::warn!("failed to read favicon {path:?}: {e}; using default");
                (include_bytes!("../static/favicon.svg").to_vec(), "image/svg+xml")
            }
        },
        None => (include_bytes!("../static/favicon.svg").to_vec(), "image/svg+xml"),
    };
    config::set_favicon(favicon_data, favicon_mime);

    let theme_vars = match args.theme_file {
        Some(path) => std::fs::read_to_string(path)?,
        None => args.theme.css().to_owned(),
    };
    let css = format!("{theme_vars}\n{BASE_CSS}\n{MARKDOWN_CSS}");

    let state = routes::AppState {
        root: args.root,
        css,
    };
    let router = routes::router(state).layer(TraceLayer::new_for_http());
    let addr = format!("{}:{}", args.bind, args.port);

    tracing::info!("listening on {addr}");

    let socket = tokio::net::TcpListener::bind(&addr).await?;
    axum::serve(socket, router)
        .with_graceful_shutdown(shutdown_signal())
        .await?;

    Ok(())
}
