use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::Arc;

use bytes::Bytes;
use folk_api::{HealthRegistry, MetricsRegistry};
use http_body_util::Full;
use hyper::body::Incoming;
use hyper::{Method, Request, Response, StatusCode};

pub struct MetricsHandler {
    pub metrics: Option<Arc<dyn MetricsRegistry>>,
    pub health: Option<Arc<dyn HealthRegistry>>,
    pub metrics_path: String,
    pub health_path: String,
    pub ready_path: String,
    pub disabled_prefixes: Vec<&'static str>,
}

impl MetricsHandler {
    pub async fn handle(
        &self,
        req: Request<Incoming>,
    ) -> Result<Response<Full<Bytes>>, Infallible> {
        let path = req.uri().path();
        if req.method() != Method::GET {
            return Ok(not_found());
        }

        if path == self.metrics_path {
            Ok(self.handle_metrics())
        } else if path == self.health_path {
            Ok(self.handle_health())
        } else if path == self.ready_path {
            Ok(self.handle_ready().await)
        } else {
            Ok(not_found())
        }
    }

    fn handle_metrics(&self) -> Response<Full<Bytes>> {
        let body = match &self.metrics {
            Some(reg) => {
                let rendered = reg.render();
                if self.disabled_prefixes.is_empty() {
                    rendered
                } else {
                    filter_metrics(&rendered, &self.disabled_prefixes)
                }
            }
            None => "# metrics registry not available\n".into(),
        };
        Response::builder()
            .status(200)
            .header("content-type", "text/plain; version=0.0.4; charset=utf-8")
            .body(Full::new(Bytes::from(body)))
            .unwrap()
    }

    /// Liveness probe: returns 200 if the process is alive.
    fn handle_health(&self) -> Response<Full<Bytes>> {
        let body = r#"{"status":"healthy"}"#;
        Response::builder()
            .status(200)
            .header("content-type", "application/json")
            .body(Full::new(Bytes::from(body)))
            .unwrap()
    }

    /// Readiness probe: runs health checks to determine if server is ready to serve.
    async fn handle_ready(&self) -> Response<Full<Bytes>> {
        let (status, body) = match &self.health {
            Some(reg) => {
                let checks = reg.check_all().await;
                let all_ok = checks.values().all(|s| s.healthy);
                let code = if all_ok {
                    StatusCode::OK
                } else {
                    StatusCode::SERVICE_UNAVAILABLE
                };
                let payload = serde_json::json!({
                    "status": if all_ok { "ready" } else { "not_ready" },
                    "checks": checks.iter().map(|(k, v)| {
                        (k.as_str(), serde_json::json!({
                            "healthy": v.healthy,
                            "message": v.message,
                        }))
                    }).collect::<HashMap<_, _>>(),
                });
                (code, payload.to_string())
            }
            None => (StatusCode::OK, r#"{"status":"ready","checks":{}}"#.into()),
        };
        Response::builder()
            .status(status)
            .header("content-type", "application/json")
            .body(Full::new(Bytes::from(body)))
            .unwrap()
    }
}

fn not_found() -> Response<Full<Bytes>> {
    Response::builder()
        .status(404)
        .body(Full::new(Bytes::new()))
        .unwrap()
}

/// Filter Prometheus text exposition output, removing metric families whose
/// name matches any of the disabled prefixes.
///
///
/// Prometheus text format groups metrics by family:
/// ```text
/// # HELP folk_http_requests_total ...
/// # TYPE folk_http_requests_total counter
/// folk_http_requests_total{...} 42
/// ```
pub fn filter_metrics(rendered: &str, disabled: &[&str]) -> String {
    let mut result = String::with_capacity(rendered.len());
    let mut skip_family = false;

    for line in rendered.lines() {
        if line.starts_with("# HELP ") || line.starts_with("# TYPE ") {
            let metric_name = line.split(' ').nth(2).unwrap_or("");
            skip_family = disabled.iter().any(|p| metric_name.starts_with(p));
        } else if !line.starts_with('#') && !line.is_empty() {
            let metric_name = line.split(['{', ' ']).next().unwrap_or("");
            skip_family = disabled.iter().any(|p| metric_name.starts_with(p));
        }

        if !skip_family {
            result.push_str(line);
            result.push('\n');
        }
    }
    result
}
