#![recursion_limit = "256"]
//! App server entry point.
//!
//! This server runs on port 3000 and serves both user and admin interfaces
//! using a unified CombinedApp with lazy-loaded admin routes.
//!
//! Both interfaces share the same `chattyness_app` database role with RLS.

#[cfg(feature = "ssr")]
mod server {
    use axum::Router;
    use clap::Parser;
    use leptos::prelude::*;
    use leptos_axum::{generate_route_list, LeptosRoutes};
    use sqlx::postgres::PgPoolOptions;
    use std::net::SocketAddr;
    use std::path::{Path, PathBuf};
    use std::sync::Arc;
    use std::time::Duration;
    use tower_http::services::ServeDir;
    use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

    use chattyness_app::{combined_shell, CombinedApp, CombinedAppState};
    use chattyness_shared::AppConfig;
    use chattyness_user_ui::api::WebSocketState;

    /// CLI arguments.
    #[derive(Parser)]
    #[command(name = "chattyness-app")]
    #[command(about = "Chattyness App Server (User + Admin UI)")]
    struct Args {
        /// Host to bind to
        #[arg(long, env = "HOST", default_value = "127.0.0.1")]
        host: String,

        /// Port to bind to
        #[arg(long, env = "APP_PORT", default_value = "3000")]
        port: u16,

        /// Database password for chattyness_app role
        #[arg(long, env = "DB_CHATTYNESS_APP")]
        db_password: String,

        /// Use secure cookies
        #[arg(long, env = "SECURE_COOKIES", default_value = "false")]
        secure_cookies: bool,

        /// Path to TOML configuration file
        #[arg(long, short = 'c', env = "CONFIG_FILE")]
        config: Option<PathBuf>,
    }

    pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
        // Load environment variables
        dotenvy::dotenv().ok();

        // Initialize logging
        tracing_subscriber::registry()
            .with(
                tracing_subscriber::EnvFilter::try_from_default_env()
                    .unwrap_or_else(|_| "chattyness_app=debug,chattyness_user_ui=debug,tower_http=debug".into()),
            )
            .with(tracing_subscriber::fmt::layer())
            .init();

        // Parse arguments
        let args = Args::parse();

        // Load configuration
        let config = AppConfig::load(args.config.as_deref())?;
        tracing::info!("Configuration loaded: {:?}", config);

        tracing::info!("Starting Chattyness App Server");

        // Create database pool for app access (fixed connection string, RLS-constrained)
        let database_url = format!(
            "postgres://chattyness_app:{}@localhost/chattyness",
            args.db_password
        );
        let pool = PgPoolOptions::new()
            .max_connections(20)
            .connect(&database_url)
            .await?;

        tracing::info!("Connected to database (app role with RLS)");

        // Startup cleanup: clear all instance_members if configured
        // Uses SECURITY DEFINER function to bypass RLS
        if config.cleanup.clear_on_startup {
            tracing::info!("Clearing stale instance_members on startup...");
            let deleted: i64 = sqlx::query_scalar("SELECT scene.clear_all_instance_members()")
                .fetch_one(&pool)
                .await?;
            tracing::info!("Cleared {} stale instance_members", deleted);
        }

        // Spawn background task for periodic stale member cleanup
        // Uses SECURITY DEFINER function to bypass RLS
        {
            let cleanup_pool = pool.clone();
            let cleanup_config = config.cleanup.clone();
            tokio::spawn(async move {
                let mut interval = tokio::time::interval(Duration::from_secs(
                    cleanup_config.reap_interval_secs,
                ));
                loop {
                    interval.tick().await;
                    let threshold = cleanup_config.stale_threshold_secs as f64;
                    match sqlx::query_scalar::<_, i64>(
                        "SELECT scene.clear_stale_instance_members($1)",
                    )
                    .bind(threshold)
                    .fetch_one(&cleanup_pool)
                    .await
                    {
                        Ok(deleted) => {
                            if deleted > 0 {
                                tracing::info!("Reaped {} stale instance_members", deleted);
                            }
                        }
                        Err(e) => {
                            tracing::error!("Stale member cleanup failed: {:?}", e);
                        }
                    }
                }
            });
            tracing::info!(
                "Started stale member reaper (interval: {}s, threshold: {}s)",
                config.cleanup.reap_interval_secs,
                config.cleanup.stale_threshold_secs
            );
        }

        // Configure Leptos
        let cargo_toml = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml");
        let conf = get_configuration(Some(cargo_toml)).unwrap();
        let leptos_options = conf.leptos_options;
        let addr = SocketAddr::new(args.host.parse()?, args.port);

        // Create session layer (shared between user and admin interfaces)
        let session_layer =
            chattyness_user_ui::auth::session::create_session_layer(pool.clone(), args.secure_cookies)
                .await;

        // Create combined app state
        let app_state = CombinedAppState {
            pool: pool.clone(),
            leptos_options: leptos_options.clone(),
        };

        // Generate routes for the combined app
        let routes = generate_route_list(CombinedApp);

        // Get site paths from Leptos config
        let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
        let workspace_root = manifest_dir.parent().unwrap().parent().unwrap();
        let site_root = workspace_root.join(&*leptos_options.site_root);
        // site-pkg-dir is now "pkg" to match wasm_split's hardcoded /pkg/ imports
        let pkg_dir = site_root.join("pkg");
        let public_dir = manifest_dir.join("public");
        // --split mode puts WASM/JS in target/site-app/pkg/ instead of configured location
        let split_pkg_dir = workspace_root.join("target/site-app/pkg");

        tracing::info!("Serving pkg files from: {}", pkg_dir.display());
        tracing::info!("Serving split WASM from: {}", split_pkg_dir.display());

        // Shared assets directory for uploaded files (realm images, etc.)
        let assets_dir = Path::new("/srv/chattyness/assets");

        // Create WebSocket state for real-time channel presence
        let ws_state = Arc::new(WebSocketState::new());

        // Create state types for each API router
        let user_api_state = chattyness_user_ui::AppState {
            pool: pool.clone(),
            leptos_options: leptos_options.clone(),
            ws_state: ws_state.clone(),
            ws_config: config.websocket.clone(),
        };
        let admin_api_state = chattyness_admin_ui::AdminAppState {
            pool: pool.clone(),
            leptos_options: leptos_options.clone(),
        };

        // Build nested API routers with their own state
        let user_api_router = chattyness_user_ui::api::api_router()
            .with_state(user_api_state);
        let admin_api_router = chattyness_admin_ui::api::admin_api_router()
            .with_state(admin_api_state);

        // Create RLS layer for row-level security
        let rls_layer = chattyness_user_ui::auth::RlsLayer::new(pool.clone());

        // Build the unified app
        // Layer order (outer to inner): session -> rls -> router
        // This ensures session is available when RLS middleware runs
        let app = Router::new()
            // API routes (with their own state)
            .nest("/api", user_api_router)
            .nest("/api/admin", admin_api_router)
            // Leptos routes with unified shell
            .leptos_routes_with_context(
                &app_state,
                routes,
                {
                    let pool = pool.clone();
                    move || {
                        provide_context(pool.clone());
                    }
                },
                {
                    let leptos_options = leptos_options.clone();
                    move || combined_shell(leptos_options.clone())
                },
            )
            .with_state(app_state)
            // Serve pkg files at /pkg (wasm_split hardcodes /pkg/ imports)
            // Fallback to split_pkg_dir for --split mode output
            .nest_service("/pkg", ServeDir::new(&pkg_dir).fallback(ServeDir::new(&split_pkg_dir)))
            // Uploaded assets (realm backgrounds, etc.) - must come before /static
            .nest_service("/static/realm", ServeDir::new(assets_dir.join("realm")))
            // Server-level assets (avatar props, etc.)
            .nest_service("/static/server", ServeDir::new(assets_dir.join("server")))
            // Also serve at /static for backwards compatibility
            .nest_service("/static", ServeDir::new(&pkg_dir).fallback(ServeDir::new(&split_pkg_dir)))
            .nest_service("/favicon.ico", tower_http::services::ServeFile::new(public_dir.join("favicon.ico")))
            // Serve admin CSS at standardized path (symlinked from owner build)
            .nest_service("/static/css/admin.css", tower_http::services::ServeFile::new(public_dir.join("admin.css")))
            // Apply middleware layers (order: session outer, rls inner)
            .layer(rls_layer)
            .layer(session_layer);

        tracing::info!("Listening on http://{}", addr);
        tracing::info!("  User UI: http://{}/", addr);
        tracing::info!("  Admin UI: http://{}/admin", addr);

        // Start server
        let listener = tokio::net::TcpListener::bind(&addr).await?;
        axum::serve(listener, app.into_make_service()).await?;

        Ok(())
    }
}

#[cfg(feature = "ssr")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    server::main().await
}

#[cfg(not(feature = "ssr"))]
fn main() {
    // This is for WASM build, which is handled by lib.rs
}
