use std::future::Future;
use std::sync::Arc;
use std::time::{Duration, Instant};

use serde::Serialize;
use serde_json::{json, Value};

use crate::json_util::now_iso;
use crate::state::AppState;
use crate::types::{OperationPerformanceSample, RequestPerformanceSample};

const MAX_PERFORMANCE_SAMPLES: usize = 3000;

fn classify_request_component(pathname: &str) -> &'static str {
    if pathname.starts_with("/api/microservices/") && pathname.contains("/proxy/") {
        "microservice_proxy"
    } else if pathname.starts_with("/api/microservices/") {
        "microservice_registry"
    } else if pathname.starts_with("/api/nodes/") {
        "node_metrics_api"
    } else if pathname == "/api/nodes" {
        "node_inventory_api"
    } else if pathname.starts_with("/api/tasks") || pathname.starts_with("/api/schedules") {
        "scheduler_api"
    } else if pathname.starts_with("/api/events") {
        "event_api"
    } else if pathname.starts_with("/api/performance") {
        "performance_api"
    } else if pathname.starts_with("/api/") {
        "core_api"
    } else if pathname.starts_with("/ws/") {
        "websocket_api"
    } else if pathname == "/logs" {
        "logs_api"
    } else {
        "core_http"
    }
}

pub fn record_request_performance(
    state: &Arc<AppState>,
    method: &str,
    pathname: &str,
    status: u16,
    duration: Duration,
) {
    let state = state.clone();
    let method = method.to_string();
    let pathname = pathname.to_string();
    tokio::spawn(async move {
        let mut samples = state.request_performance_samples.lock().await;
        samples.push(RequestPerformanceSample {
            at: now_iso(),
            component: classify_request_component(&pathname).to_string(),
            method,
            path: pathname,
            status,
            duration_ms: duration.as_secs_f64() * 1000.0,
            ok: status < 400,
        });
        if samples.len() > MAX_PERFORMANCE_SAMPLES {
            let drain = samples.len() - MAX_PERFORMANCE_SAMPLES;
            samples.drain(0..drain);
        }
    });
}

pub async fn record_operation_performance(
    state: &Arc<AppState>,
    service: &str,
    operation: &str,
    detail: &str,
    duration: Duration,
    ok: bool,
) {
    let mut samples = state.operation_performance_samples.lock().await;
    samples.push(OperationPerformanceSample {
        at: now_iso(),
        service: service.to_string(),
        operation: operation.to_string(),
        duration_ms: duration.as_secs_f64() * 1000.0,
        ok,
        detail: if detail.len() > 260 {
            format!("{}...", &detail[..257.min(detail.len())])
        } else {
            detail.to_string()
        },
    });
    if samples.len() > MAX_PERFORMANCE_SAMPLES {
        let drain = samples.len() - MAX_PERFORMANCE_SAMPLES;
        samples.drain(0..drain);
    }
}

pub async fn with_operation<T, Fut>(
    state: &Arc<AppState>,
    service: &str,
    operation: &str,
    detail: &str,
    fut: Fut,
) -> anyhow::Result<T>
where
    Fut: Future<Output = anyhow::Result<T>>,
{
    let started = Instant::now();
    match fut.await {
        Ok(value) => {
            record_operation_performance(
                state,
                service,
                operation,
                detail,
                started.elapsed(),
                true,
            )
            .await;
            Ok(value)
        }
        Err(error) => {
            record_operation_performance(
                state,
                service,
                operation,
                &error.to_string(),
                started.elapsed(),
                false,
            )
            .await;
            Err(error)
        }
    }
}

fn percentile(values: &[f64], ratio: f64) -> f64 {
    if values.is_empty() {
        return 0.0;
    }
    let mut sorted = values.to_vec();
    sorted.sort_by(|left, right| left.partial_cmp(right).unwrap_or(std::cmp::Ordering::Equal));
    let index = ((sorted.len() as f64 * ratio).ceil() as usize)
        .saturating_sub(1)
        .min(sorted.len() - 1);
    sorted[index]
}

fn average(values: &[f64]) -> f64 {
    if values.is_empty() {
        0.0
    } else {
        values.iter().sum::<f64>() / values.len() as f64
    }
}

fn round_ms(value: f64) -> f64 {
    (value * 10.0).round() / 10.0
}

#[derive(Default, Serialize)]
#[serde(rename_all = "camelCase")]
struct RequestSummary {
    component: String,
    request_count: usize,
    failure_count: usize,
    failure_rate: f64,
    average_latency_ms: f64,
    p95_latency_ms: f64,
    max_latency_ms: f64,
}

pub async fn get_performance(state: &Arc<AppState>) -> anyhow::Result<Value> {
    let request_samples = state.request_performance_samples.lock().await.clone();
    let operation_samples = state.operation_performance_samples.lock().await.clone();
    let mut request_groups =
        std::collections::BTreeMap::<String, Vec<RequestPerformanceSample>>::new();
    for sample in &request_samples {
        request_groups
            .entry(sample.component.clone())
            .or_default()
            .push(sample.clone());
    }
    let mut component_summary: Vec<Value> = request_groups
        .into_iter()
        .map(|(component, rows)| {
            let durations = rows.iter().map(|row| row.duration_ms).collect::<Vec<_>>();
            let failure_count = rows.iter().filter(|row| !row.ok).count();
            json!({
                "component": component,
                "requestCount": rows.len(),
                "failureCount": failure_count,
                "failureRate": if rows.is_empty() { 0.0 } else { failure_count as f64 / rows.len() as f64 },
                "averageLatencyMs": round_ms(average(&durations)),
                "p95LatencyMs": round_ms(percentile(&durations, 0.95)),
                "maxLatencyMs": round_ms(durations.into_iter().fold(0.0, f64::max)),
            })
        })
        .collect();
    component_summary.sort_by_key(|item| {
        std::cmp::Reverse(
            item.get("requestCount")
                .and_then(Value::as_u64)
                .unwrap_or(0),
        )
    });

    let mut operation_groups =
        std::collections::BTreeMap::<String, Vec<OperationPerformanceSample>>::new();
    for sample in &operation_samples {
        operation_groups
            .entry(format!("{}:{}", sample.service, sample.operation))
            .or_default()
            .push(sample.clone());
    }
    let mut operation_summary: Vec<Value> = operation_groups
        .into_iter()
        .map(|(key, rows)| {
            let (service, operation) = key.split_once(':').unwrap_or((&key, ""));
            let durations = rows.iter().map(|row| row.duration_ms).collect::<Vec<_>>();
            let failure_count = rows.iter().filter(|row| !row.ok).count();
            json!({
                "service": service,
                "operation": operation,
                "count": rows.len(),
                "failureCount": failure_count,
                "averageLatencyMs": round_ms(average(&durations)),
                "p95LatencyMs": round_ms(percentile(&durations, 0.95)),
                "maxLatencyMs": round_ms(durations.into_iter().fold(0.0, f64::max)),
            })
        })
        .collect();
    operation_summary.sort_by_key(|item| {
        std::cmp::Reverse(item.get("count").and_then(Value::as_u64).unwrap_or(0))
    });

    let pgdata = crate::overview::get_pgdata_usage(state)
        .await
        .unwrap_or_else(|error| json!({ "ok": false, "error": error.to_string() }));
    let codex_queue_storage = get_codex_queue_storage_performance(state).await;
    let recent_failures: Vec<Value> = request_samples
        .iter()
        .filter(|sample| !sample.ok)
        .rev()
        .take(20)
        .map(|sample| serde_json::to_value(sample).unwrap_or(Value::Null))
        .collect();
    let cutoff = chrono::Utc::now().timestamp_millis() - 10 * 60 * 1000;
    let recent_slow_operations: Vec<Value> = operation_samples
        .iter()
        .filter(|sample| {
            chrono::DateTime::parse_from_rfc3339(&sample.at)
                .map(|at| at.timestamp_millis() >= cutoff)
                .unwrap_or(false)
        })
        .rev()
        .take(20)
        .map(|sample| serde_json::to_value(sample).unwrap_or(Value::Null))
        .collect();

    Ok(json!({
        "ok": true,
        "service": "backend-core",
        "generatedAt": now_iso(),
        "startedAt": state.service_started_at.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
        "uptimeSeconds": (chrono::Utc::now() - state.service_started_at).num_seconds(),
        "requests": {
            "sampleCount": request_samples.len(),
            "componentSummary": component_summary,
            "recentFailures": recent_failures,
        },
        "operations": {
            "sampleCount": operation_samples.len(),
            "summary": operation_summary,
            "recentSlowOperations": recent_slow_operations,
        },
        "process": process_memory_json(),
        "database": {
            "ready": state.db_ready(),
            "pgdata": pgdata,
            "codexQueueStorage": codex_queue_storage,
        },
    }))
}

fn process_memory_json() -> Value {
    let status = std::fs::read_to_string("/proc/self/status").unwrap_or_default();
    let mut rss = 0_u64;
    for line in status.lines() {
        if let Some(rest) = line.strip_prefix("VmRSS:") {
            rss = rest
                .split_whitespace()
                .next()
                .and_then(|text| text.parse::<u64>().ok())
                .unwrap_or(0)
                * 1024;
        }
    }
    json!({
        "rssBytes": rss,
        "heapUsedBytes": 0,
        "heapTotalBytes": 0,
        "externalBytes": 0,
        "arrayBuffersBytes": 0,
    })
}

async fn get_codex_queue_storage_performance(state: &Arc<AppState>) -> Value {
    let result = async {
        let client = state.pool.get().await?;
        let rows = client.query("SELECT status, count(*)::int AS count FROM unidesk_codex_queue_tasks GROUP BY status ORDER BY status ASC", &[]).await?;
        anyhow::Ok(rows)
    }.await;
    match result {
        Ok(rows) => {
            let mut total = 0_i64;
            let mut counts = serde_json::Map::new();
            for row in rows {
                let status: String = row.get("status");
                let count: i32 = row.get("count");
                total += i64::from(count);
                counts.insert(status, json!(count));
            }
            json!({ "ok": true, "table": "unidesk_codex_queue_tasks", "counts": counts, "total": total })
        }
        Err(error) => {
            json!({ "ok": false, "table": "unidesk_codex_queue_tasks", "error": error.to_string() })
        }
    }
}
