use axum::routing::get;
use axum::Router;
use tokio::signal;
use tower_http::catch_panic::CatchPanicLayer;

use clean_copy_server::config;
use clean_copy_server::crypto;
use clean_copy_server::db;
use clean_copy_server::handlers;
use clean_copy_server::AppState;

async fn shutdown_signal() {
  let ctrl_c = async {
    signal::ctrl_c()
      .await
      .expect("Failed to install Ctrl+C handler");
  };

  #[cfg(unix)]
  let terminate = async {
    signal::unix::signal(signal::unix::SignalKind::terminate())
      .expect("Failed to install SIGTERM handler")
      .recv()
      .await;
  };

  #[cfg(not(unix))]
  let terminate = std::future::pending::<()>();

  tokio::select! {
    _ = ctrl_c => tracing::info!("Received Ctrl+C, shutting down"),
    _ = terminate => tracing::info!("Received SIGTERM, shutting down"),
  }
}

#[tokio::main]
async fn main() {
  tracing_subscriber::fmt::init();

  let config = config::Config::from_env();

  let pool = db::create_pool(&config.database_url)
    .await
    .expect("Failed to initialize database");

  let signing_key = crypto::load_signing_key(&config.signing_key_hex)
    .expect("Failed to load signing key from SIGNING_KEY_HEX");

  let stripe_client = stripe::Client::new(&config.stripe_secret_key);

  let state = AppState {
    config: config.clone(),
    db: pool.clone(),
    signing_key,
    stripe_client,
  };

  let app = Router::new()
    .route("/", get(handlers::landing::landing_page))
    .route("/docs", get(handlers::docs::docs_page))
    .route("/terms", get(handlers::terms::terms_page))
    .route("/refunds", get(handlers::refunds::refunds_page))
    .route("/success", get(handlers::success::success_page))
    .route("/privacy", get(handlers::privacy::privacy_page))
    .route(
      "/api/activate",
      axum::routing::post(handlers::activate::activate),
    )
    .route("/api/checkout", get(handlers::checkout::checkout))
    .route(
      "/releases/download/{version}/{arch}/{filename}",
      get(handlers::download::download),
    )
    .route(
      "/updates/{target}/{arch}/{version}",
      get(handlers::updater::updater),
    )
    .route("/healthz", get(handlers::healthz::healthz))
    .route("/404", get(handlers::errors::not_found))
    .route("/500", get(handlers::errors::internal_error_page))
    .nest_service(
      "/static",
      tower_http::services::ServeDir::new(&config.static_dir),
    )
    .layer(CatchPanicLayer::custom(handlers::errors::internal_error))
    .fallback(handlers::errors::not_found)
    .with_state(state);

  let addr = format!("0.0.0.0:{}", config.port);

  let listener = tokio::net::TcpListener::bind(&addr)
    .await
    .expect("Failed to bind to address");

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

  axum::serve(listener, app)
    .with_graceful_shutdown(shutdown_signal())
    .await
    .unwrap();

  pool.close().await;

  tracing::info!("Database connection closed");
}
