mod auth_guard;
mod business_metrics;
mod routes;
mod state;

use std::sync::Arc;

use axum::{Router, routing::{get, post}};
use tower_http::services::ServeDir;
use openid::DiscoveredClient;
use state::AppState;
use tower_sessions::{MemoryStore, SessionManagerLayer, cookie::SameSite};
use irc_now_common::auth::OidcConfig;
use irc_now_common::db::DbConfig;
use sqlx::PgPool;

async fn health() -> &'static str {
    "ok"
}

async fn cleanup_expired_pastes(db: PgPool) {
    let mut interval = tokio::time::interval(std::time::Duration::from_secs(3600));
    loop {
        interval.tick().await;
        match sqlx::query("DELETE FROM pastes WHERE expires_at IS NOT NULL AND expires_at < now()")
            .execute(&db)
            .await
        {
            Ok(result) => {
                if result.rows_affected() > 0 {
                    tracing::info!("cleaned up {} expired pastes", result.rows_affected());
                }
            }
            Err(e) => tracing::error!("paste cleanup failed: {e}"),
        }
    }
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    let db = DbConfig::from_env("PASTE").connect().await.unwrap();
    let oidc = OidcConfig::from_env();

    let issuer_url = url::Url::parse(&oidc.issuer_url).expect("invalid OIDC issuer URL");

    tracing::info!("discovering OIDC provider at {issuer_url}");
    let oidc_client = DiscoveredClient::discover(
        oidc.client_id.clone(),
        oidc.client_secret.clone(),
        Some(oidc.redirect_url.clone()),
        issuer_url,
    )
    .await
    .expect("OIDC discovery failed");

    tracing::info!("OIDC provider discovered");

    let state = AppState {
        db: db.clone(),
        oidc,
        oidc_client: Arc::new(oidc_client),
    };

    let metrics_db = db.clone();
    tokio::spawn(cleanup_expired_pastes(db));
    tokio::spawn(business_metrics::record_paste_metrics(metrics_db));

    let metrics_handle = irc_now_common::metrics::setup_metrics_recorder();

    let session_store = MemoryStore::default();
    let session_layer = SessionManagerLayer::new(session_store).with_same_site(SameSite::Lax);

    let app = Router::new()
        .route("/health", get(health))
        .route("/metrics", get(irc_now_common::metrics::metrics_handler))
        .route("/", get(routes::paste::index).post(routes::paste::create))
        .route("/my", get(routes::paste::my_pastes))
        .route("/{id}", get(routes::paste::view))
        .route("/{id}/raw", get(routes::paste::raw))
        .route("/{id}/delete", post(routes::paste::delete))
        .route("/auth/login", get(routes::auth::login))
        .route("/auth/callback", get(routes::auth::callback))
        .route("/auth/logout", get(routes::auth::logout))
        .nest_service("/static", ServeDir::new("static"))
        .layer(session_layer)
        .layer(axum::middleware::from_fn(irc_now_common::metrics::track_metrics))
        .layer(Extension(metrics_handle))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
    tracing::info!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}
