mod api;
mod auth_guard;
mod business_metrics;
mod db;
mod irc;
mod lua;
mod manager;
mod routes;
mod state;

use std::sync::Arc;

use axum::{Router, routing::{get, post, put}};
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_old_logs(db: PgPool) {
    let mut interval = tokio::time::interval(std::time::Duration::from_secs(3600));
    loop {
        interval.tick().await;
        match db::cleanup_old_logs(&db, 7).await {
            Ok(n) => {
                if n > 0 {
                    tracing::info!("cleaned up {n} old bot log entries");
                }
            }
            Err(e) => tracing::error!("log 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("BOT").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 bot_manager = Arc::new(manager::BotManager::new(db.clone()));

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

    irc_now_common::metrics::setup_metrics_recorder();

    let metrics_db = db.clone();
    tokio::spawn(cleanup_old_logs(db));
    tokio::spawn(business_metrics::record_bot_metrics(metrics_db));

    {
        let mgr = bot_manager.clone();
        tokio::spawn(async move {
            mgr.start_all_enabled().await;
        });
    }

    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(|| async { axum::response::Redirect::temporary("/my") }))
        .route("/my", get(routes::bot::list))
        .route("/create", get(routes::bot::create_form).post(routes::bot::create))
        .route("/{id}", get(routes::bot::detail))
        .route("/{id}/update", post(routes::bot::update))
        .route("/{id}/delete", post(routes::bot::delete))
        .route("/{id}/start", post(routes::bot::start))
        .route("/{id}/stop", post(routes::bot::stop))
        .route("/{id}/scripts/create", get(routes::script::create_form).post(routes::script::create))
        .route("/{id}/scripts/{sid}", get(routes::script::edit))
        .route("/{id}/scripts/{sid}/update", post(routes::script::update))
        .route("/{id}/scripts/{sid}/delete", post(routes::script::delete))
        .route("/{id}/logs", get(routes::logs::history))
        .route("/{id}/logs/ws", get(routes::logs::ws_upgrade))
        .route("/{id}/kv", get(routes::kv::list))
        .route("/{id}/kv/set", post(routes::kv::set))
        .route("/{id}/kv/{key}/delete", post(routes::kv::delete))
        .route("/auth/login", get(routes::auth::login))
        .route("/auth/callback", get(routes::auth::callback))
        .route("/auth/logout", get(routes::auth::logout))
        .route("/api/bots", get(api::list_bots).post(api::create_bot))
        .route("/api/bots/{id}", get(api::get_bot).put(api::update_bot).delete(api::delete_bot))
        .route("/api/bots/{id}/start", post(api::start_bot))
        .route("/api/bots/{id}/stop", post(api::stop_bot))
        .route("/api/bots/{id}/scripts", get(api::list_scripts).post(api::create_script))
        .route("/api/bots/{id}/scripts/{sid}", get(api::get_script).put(api::update_script).delete(api::delete_script))
        .route("/api/bots/{id}/logs", get(api::list_logs))
        .route("/api/bots/{id}/kv", get(api::list_kv))
        .route("/api/bots/{id}/kv/{key}", put(api::set_kv).delete(api::delete_kv))
        .route("/api/usage/{user_id}", get(api::user_usage))
        .nest_service("/static", ServeDir::new("static"))
        .layer(session_layer)
        .layer(axum::middleware::from_fn(irc_now_common::metrics::track_metrics))
        .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();
}
