use dioxus::prelude::*;

use axum::routing::get;
use axum::{middleware, Extension};
use time::Duration;
use tower_sessions::{cookie::Key, MemoryStore, SessionManagerLayer};

use crate::infrastructure::{
    auth_callback, auth_login,
    config::{KeycloakConfig, LlmProvidersConfig, ServiceUrls, SmtpConfig, StripeConfig},
    database::Database,
    logout, require_auth,
    server_state::{ServerState, ServerStateInner},
    PendingOAuthStore,
};

/// Start the Axum server with Dioxus fullstack, session management,
/// MongoDB, and Keycloak OAuth routes.
///
/// Loads all configuration from environment variables once, connects
/// to MongoDB, and builds a [`ServerState`] shared across every request.
///
/// # Errors
///
/// Returns `Error` if the tokio runtime, config loading, DB connection,
/// or TCP listener fails.
pub fn server_start(app: fn() -> Element) -> Result<(), super::Error> {
    tokio::runtime::Runtime::new()?.block_on(async move {
        // Load .env once at startup.
        dotenvy::dotenv().ok();

        // ---- Load and leak config structs for 'static lifetime ----
        let keycloak: &'static KeycloakConfig = Box::leak(Box::new(KeycloakConfig::from_env()?));
        let smtp: &'static SmtpConfig = Box::leak(Box::new(SmtpConfig::from_env()?));
        let services: &'static ServiceUrls = Box::leak(Box::new(ServiceUrls::from_env()?));
        let stripe: &'static StripeConfig = Box::leak(Box::new(StripeConfig::from_env()?));
        let llm_providers: &'static LlmProvidersConfig =
            Box::leak(Box::new(LlmProvidersConfig::from_env()?));

        tracing::info!("Configuration loaded");

        // ---- Connect to MongoDB ----
        let mongo_uri =
            std::env::var("MONGODB_URI").unwrap_or_else(|_| "mongodb://localhost:27017".into());
        let mongo_db = std::env::var("MONGODB_DATABASE").unwrap_or_else(|_| "certifai".into());

        let db = Database::connect(&mongo_uri, &mongo_db).await?;
        tracing::info!("Connected to MongoDB (database: {mongo_db})");

        // ---- Build ServerState ----
        let server_state: ServerState = ServerStateInner {
            db,
            keycloak,
            smtp,
            services,
            stripe,
            llm_providers,
        }
        .into();

        // ---- Session layer ----
        let key = Key::generate();
        let store = MemoryStore::default();
        let session = SessionManagerLayer::new(store)
            .with_secure(false)
            // Lax is required so the browser sends the session cookie
            // on the redirect back from Keycloak (cross-origin GET).
            .with_same_site(tower_sessions::cookie::SameSite::Lax)
            .with_expiry(tower_sessions::Expiry::OnInactivity(Duration::hours(24)))
            .with_signed(key);

        // ---- Build router ----
        let addr = dioxus_cli_config::fullstack_address_or_localhost();
        let listener = tokio::net::TcpListener::bind(addr).await?;

        // Layers wrap in reverse order: session (outermost) -> auth
        // middleware -> extensions -> route handlers. The session layer
        // must be outermost so the `Session` extractor is available to
        // the auth middleware, which gates all `/api/` server function
        // routes (except `check-auth`).
        let router = axum::Router::new()
            .route("/auth", get(auth_login))
            .route("/auth/callback", get(auth_callback))
            .route("/logout", get(logout))
            .serve_dioxus_application(ServeConfig::new(), app)
            .layer(Extension(PendingOAuthStore::default()))
            .layer(Extension(server_state))
            .layer(middleware::from_fn(require_auth))
            .layer(session);

        tracing::info!("Serving at {addr}");
        axum::serve(listener, router.into_make_service()).await?;

        Ok(())
    })
}
