mod alerts;
mod app;
mod checker;
mod crawler;
mod db;
mod lighthouse;
mod middleware;
mod migrate;
mod models;
mod pdf;
mod render;
mod routes;
mod scheduler;
mod templates;

pub use app::{AppState, Config};

use std::net::SocketAddr;
use std::path::PathBuf;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    dotenvy::dotenv().ok();
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info,sqlx=warn")),
        )
        .init();

    // rustls 0.23 doesn't pick a CryptoProvider on its own when neither
    // the `ring` nor the `aws-lc-rs` feature is enabled at the rustls
    // crate level. The phased prober (src/checker.rs) builds rustls
    // ClientConfigs directly, which would panic on first probe without
    // this. Install once at startup; reqwest/lettre's TLS paths are
    // unaffected because they ship their own provider via hyper-rustls.
    let _ = rustls::crypto::ring::default_provider().install_default();

    // Subcommand dispatch. Anything besides a known subcommand falls through to the server.
    let mut argv = std::env::args().skip(1);
    if let Some(first) = argv.next() {
        match first.as_str() {
            "migrate" => return run_migrate(argv.collect()).await,
            "preview-email" => return run_preview_email(argv.collect()),
            "--help" | "-h" => {
                print_usage();
                return Ok(());
            }
            other if !other.is_empty() => {
                eprintln!("unknown subcommand: {other}");
                print_usage();
                std::process::exit(2);
            }
            _ => {}
        }
    }

    let port: u16 = std::env::var("PORT")
        .ok()
        .and_then(|v| v.parse().ok())
        .unwrap_or(8000);

    let state = AppState::from_env().await?;

    // Reset wedged crawl/lighthouse rows from a prior crash, then spawn the
    // scheduler loop alongside the HTTP server.
    scheduler::reset_states_on_boot(&state.pool).await?;
    let scheduler_handle = scheduler::spawn(state.pool.clone(), state.config.clone());

    let router = app::router(state);

    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    let listener = tokio::net::TcpListener::bind(addr).await?;
    tracing::info!("status listening on http://{addr}");
    axum::serve(listener, router).await?;
    drop(scheduler_handle);
    Ok(())
}

fn print_usage() {
    eprintln!(
        "status: single-binary axum uptime monitor\n\
         \n\
         Usage:\n  \
           status                              run the HTTP server + scheduler\n  \
           status migrate <path> [--force]     import a Django status SQLite\n  \
           status preview-email <down|recovery> render an alert HTML to stdout\n"
    );
}

fn run_preview_email(args: Vec<String>) -> anyhow::Result<()> {
    let kind = args
        .first()
        .map(String::as_str)
        .ok_or_else(|| anyhow::anyhow!("usage: status preview-email <down|recovery>"))?;
    let base_url = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost:8000".to_string());
    let html = alerts::render_preview_html(kind, &base_url)?;
    print!("{html}");
    Ok(())
}

async fn run_migrate(args: Vec<String>) -> anyhow::Result<()> {
    let mut source: Option<PathBuf> = None;
    let mut force = false;
    for arg in args {
        match arg.as_str() {
            "--force" | "-f" => force = true,
            "--help" | "-h" => {
                eprintln!("Usage: status migrate <path-to-django-sqlite3> [--force]");
                return Ok(());
            }
            v if v.starts_with("--") => anyhow::bail!("unknown migrate flag: {v}"),
            v => {
                if source.is_some() {
                    anyhow::bail!("migrate takes a single source path");
                }
                source = Some(PathBuf::from(v));
            }
        }
    }
    let source = source.ok_or_else(|| {
        anyhow::anyhow!("usage: status migrate <path-to-django-sqlite3> [--force]")
    })?;
    migrate::run(source, force).await
}
