pub mod admin;
pub mod auth;
pub mod bootstrap_assets;
pub mod cloud_init;
pub mod cluster;
pub mod config;
pub mod local_storage;
pub mod phone_home;
pub mod state;
pub mod storage;
pub mod tls;
pub mod validation;

use axum::{
    routing::{get, post},
    Router,
};
use std::sync::Arc;
use tracing::info;

use crate::{config::Config, state::AppState};

/// Build the Axum router with all API routes
pub fn build_router(state: Arc<AppState>) -> Router {
    Router::new()
        // Health check
        .route("/health", get(health_check))
        // Phone Home API (node registration)
        .route("/api/v1/phone-home", post(phone_home::phone_home))
        .route(
            "/api/v1/cloud-init/:machine_id/meta-data",
            get(cloud_init::meta_data),
        )
        .route(
            "/api/v1/cloud-init/:machine_id/user-data",
            get(cloud_init::user_data),
        )
        .route(
            "/api/v1/bootstrap/flake-bundle",
            get(bootstrap_assets::flake_bundle),
        )
        // Admin API (node management)
        .route("/api/v1/admin/nodes", post(admin::pre_register))
        .route("/api/v1/admin/nodes", get(admin::list_nodes))
        .with_state(state)
}

/// Health check endpoint
async fn health_check() -> &'static str {
    "OK"
}

/// Run the Deployer server
pub async fn run(config: Config) -> anyhow::Result<()> {
    let bind_addr = config.bind_addr;

    // Create application state
    let mut state = AppState::with_config(config);

    if state.config.allow_unauthenticated {
        tracing::warn!("Deployer running with allow_unauthenticated=true (unsafe)");
    } else if state.config.bootstrap_token.is_none() {
        tracing::warn!("Deployer requires bootstrap_token but none is configured");
    }

    if state.config.admin_token.is_none() {
        if state.config.allow_admin_fallback {
            tracing::warn!("admin_token not set; admin API will fall back to bootstrap_token");
        } else {
            tracing::warn!(
                "admin_token not set; admin API disabled unless allow_admin_fallback=true"
            );
        }
    } else if state.config.admin_token == state.config.bootstrap_token {
        tracing::warn!(
            "DEPLOYER_ADMIN_TOKEN matches bootstrap token; consider separating privileges"
        );
    }

    if state.config.cluster_id.is_none() {
        tracing::warn!(
            "cluster_id not set; cluster node state won't be written to ultracloud/clusters"
        );
    }

    // Initialize ChainFire storage
    if let Err(e) = state.init_storage().await {
        tracing::warn!(error = %e, "ChainFire storage initialization failed");
    }

    if state.config.require_chainfire && !state.has_storage() {
        return Err(anyhow::anyhow!(
            "ChainFire storage is required but unavailable. Configure chainfire.endpoints or disable require_chainfire for dev mode."
        ));
    }

    let state = Arc::new(state);

    // Build router
    let app = build_router(state);

    // Create TCP listener
    let listener = tokio::net::TcpListener::bind(bind_addr).await?;

    info!("Deployer server listening on {}", bind_addr);

    // Run server
    axum::serve(listener, app).await?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::http::StatusCode;
    use tower::ServiceExt;

    #[tokio::test]
    async fn test_health_check() {
        let state = Arc::new(AppState::new());
        let app = build_router(state);

        let response = app
            .oneshot(
                axum::http::Request::builder()
                    .uri("/health")
                    .body(axum::body::Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::OK);
    }
}
