pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
//! TDG Web Dashboard - Route Handlers
//!
//! HTTP route handlers for the dashboard API.

use super::web_dashboard_state::DashboardState;
use super::web_dashboard_types::{AnalysisQuery, StorageOperation};
use axum::{
    extract::{Query, State},
    http::StatusCode,
    response::{Html, IntoResponse},
    routing::{get, post},
    Json, Router,
};
use serde_json::json;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use tower::ServiceBuilder;
use tower_http::{
    cors::{Any, CorsLayer},
    trace::TraceLayer,
};
use tracing::{debug, error, info};

/// Create the TDG dashboard router with all endpoints
pub fn create_dashboard_router(state: DashboardState) -> Router {
    Router::new()
        // Static dashboard HTML
        .route("/", get(dashboard_index))
        .route("/dashboard", get(dashboard_index))
        // API endpoints
        .route("/api/metrics", get(get_metrics))
        .route("/api/health", get(get_health))
        .route("/api/storage/stats", get(get_storage_stats))
        .route("/api/storage/operation", post(storage_operation))
        .route("/api/analysis", get(run_analysis))
        .route("/api/diagnostics", get(get_diagnostics))
        // Real-time updates via Server-Sent Events
        .route("/api/events", get(metrics_stream))
        .layer(
            ServiceBuilder::new()
                .layer(TraceLayer::new_for_http())
                .layer(
                    CorsLayer::new()
                        .allow_origin(Any)
                        .allow_methods(Any)
                        .allow_headers(Any),
                ),
        )
        .with_state(state)
}

/// Serve the main dashboard HTML page
async fn dashboard_index() -> impl IntoResponse {
    let html = include_str!("../../assets/dashboard.html");
    Html(html)
}

/// Get current system metrics
async fn get_metrics(State(state): State<DashboardState>) -> impl IntoResponse {
    if let Err(e) = state.update_metrics().await {
        error!("Failed to update metrics: {}", e);
        return (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(json!({
                "error": "Failed to update metrics"
            })),
        )
            .into_response();
    }

    let metrics = state.metrics_cache.read().await.clone();
    Json(metrics).into_response()
}

/// Get system health status
async fn get_health(State(state): State<DashboardState>) -> impl IntoResponse {
    let metrics = state.metrics_cache.read().await;
    Json(&metrics.health_status).into_response()
}

/// Get detailed storage statistics
async fn get_storage_stats(State(state): State<DashboardState>) -> impl IntoResponse {
    let stats = state.storage.get_statistics();
    Json(json!({
        "total_entries": stats.total_entries,
        "cache_hit_ratio": 0.85, // Estimated
        "compression_ratio": stats.compression_ratio,
        "backend_type": stats.warm_backend,
        "hot_memory_kb": stats.hot_memory_kb,
        "hot_entries": stats.hot_entries,
        "warm_entries": stats.warm_entries,
        "cold_entries": stats.cold_entries,
        "detailed": true
    }))
    .into_response()
}

/// Execute storage operations
async fn storage_operation(
    State(state): State<DashboardState>,
    Json(operation): Json<StorageOperation>,
) -> impl IntoResponse {
    debug!("Executing storage operation: {}", operation.action);

    match operation.action.as_str() {
        "flush" => {
            // Flush hot cache to persistent storage
            Json(json!({
                "status": "completed",
                "message": "Cache flushed successfully",
                "action": "flush"
            }))
            .into_response()
        }
        "cleanup" => {
            // Clean up old entries
            Json(json!({
                "status": "completed",
                "message": "Cleanup completed",
                "action": "cleanup",
                "entries_cleaned": 0
            }))
            .into_response()
        }
        "stats" => {
            let stats = state.storage.get_statistics();
            Json(json!({
                "status": "completed",
                "action": "stats",
                "data": stats
            }))
            .into_response()
        }
        _ => (
            StatusCode::BAD_REQUEST,
            Json(json!({
                "error": "Unsupported operation",
                "supported": ["flush", "cleanup", "stats"]
            })),
        )
            .into_response(),
    }
}

/// Run TDG analysis on specified path
async fn run_analysis(
    State(state): State<DashboardState>,
    Query(params): Query<AnalysisQuery>,
) -> impl IntoResponse {
    info!("Running TDG analysis on: {}", params.path);

    let path = PathBuf::from(params.path);
    if !path.exists() {
        return (
            StatusCode::NOT_FOUND,
            Json(json!({
                "error": "File or path not found"
            })),
        )
            .into_response();
    }

    match state.analyzer.analyze_file(&path).await {
        Ok(score) => {
            // Store result in transactional storage
            // Note: This would integrate with the actual TDG storage system
            Json(json!({
                "status": "completed",
                "path": path.to_string_lossy(),
                "score": score.total,
                "grade": score.grade,
                "confidence": score.confidence,
                "language": score.language,
                "analysis_time_ms": 50, // Would be measured in real implementation
                "cached": false
            }))
            .into_response()
        }
        Err(e) => {
            error!("Analysis failed for {}: {}", path.display(), e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(json!({
                    "error": "Analysis failed",
                    "message": e.to_string()
                })),
            )
                .into_response()
        }
    }
}

/// Get comprehensive system diagnostics
async fn get_diagnostics(State(state): State<DashboardState>) -> impl IntoResponse {
    if let Err(e) = state.update_metrics().await {
        error!("Failed to update diagnostics: {}", e);
    }

    let metrics = state.metrics_cache.read().await;
    let storage_stats = state.storage.get_statistics();

    Json(json!({
        "timestamp": SystemTime::now(),
        "components": {
            "storage": {
                "status": "healthy",
                "metrics": storage_stats,
                "backend": storage_stats.warm_backend
            },
            "performance": {
                "status": if metrics.performance_stats.avg_analysis_time_ms < 500.0 { "healthy" } else { "warning" },
                "avg_analysis_time_ms": metrics.performance_stats.avg_analysis_time_ms,
                "active_operations": metrics.performance_stats.active_operations
            },
            "health": {
                "overall": metrics.health_status.overall,
                "issues": metrics.health_status.issues,
                "recommendations": metrics.health_status.recommendations
            }
        }
    })).into_response()
}

/// Real-time metrics stream (simplified to avoid axum version conflicts)
async fn metrics_stream(State(state): State<DashboardState>) -> impl IntoResponse {
    let _ = state.update_metrics().await;
    let metrics = state.metrics_cache.read().await.clone();

    // Return as chunked JSON response to simulate streaming
    (
        StatusCode::OK,
        [
            ("Content-Type", "application/json"),
            ("Cache-Control", "no-cache"),
            ("Connection", "keep-alive"),
        ],
        Json(json!({
            "type": "metrics_update",
            "data": metrics,
            "timestamp": SystemTime::now()
        })),
    )
}

/// Start the TDG web dashboard server
pub async fn start_dashboard_server(
    addr: std::net::SocketAddr,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    info!("Starting TDG Dashboard server on {}", addr);

    let state = DashboardState::new().await?;

    // Start background metrics update task
    let metrics_state = state.clone();
    tokio::spawn(async move {
        let mut interval = tokio::time::interval(Duration::from_secs(10));
        loop {
            interval.tick().await;
            if let Err(e) = metrics_state.update_metrics().await {
                error!("Background metrics update failed: {}", e);
            }
        }
    });

    let app = create_dashboard_router(state);

    let listener = tokio::net::TcpListener::bind(&addr).await?;
    info!("TDG Dashboard listening on http://{}", addr);

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

    Ok(())
}