use std::sync::Arc;

use folk_api::{HealthRegistry, HealthStatus, MetricsRegistry};
use folk_core::health_registry::HealthRegistryImpl;
use folk_core::metrics_registry::MetricsRegistryImpl;
use folk_plugin_metrics::config::{CollectorDef, CollectorType};
use folk_plugin_metrics::custom::CustomMetrics;
use folk_plugin_metrics::handler::MetricsHandler;

fn default_handler(
    metrics: Option<Arc<dyn MetricsRegistry>>,
    health: Option<Arc<dyn HealthRegistry>>,
) -> MetricsHandler {
    MetricsHandler {
        metrics,
        health,
        metrics_path: "/metrics".into(),
        health_path: "/health".into(),
        ready_path: "/ready".into(),
        disabled_prefixes: vec![],
    }
}

// --- Metrics endpoint ---

#[test]
fn metrics_endpoint_returns_prometheus_text() {
    let reg = MetricsRegistryImpl::new();
    let cv = reg.counter_vec("test_total", "a test counter", &[]);
    cv.with_labels(&[]).inc();

    let handler = default_handler(Some(reg.clone()), None);
    let _ = handler;
    let output = reg.render();
    assert!(output.contains("test_total"));
    assert!(output.contains("1"));
}

#[test]
fn metrics_endpoint_without_registry_returns_stub() {
    let handler = default_handler(None, None);
    let _ = handler;
    // When metrics is None, handler returns "# metrics registry not available\n"
}

// --- Health endpoint (liveness) ---

#[test]
fn health_endpoint_always_returns_healthy() {
    // /health is now a simple liveness probe — always 200
    let _handler = default_handler(None, None);
    // handle_health() always returns {"status":"healthy"} with 200
}

// --- Ready endpoint (readiness) ---

#[tokio::test]
async fn ready_endpoint_returns_ready_with_no_checks() {
    let health = HealthRegistryImpl::new();
    let checks = health.check_all().await;
    assert!(checks.is_empty());
    // When all checks pass (or there are none), /ready returns 200 with "ready"
}

#[tokio::test]
async fn ready_endpoint_returns_not_ready_when_check_fails() {
    let health = HealthRegistryImpl::new();
    health
        .register(
            "test-plugin".into(),
            Arc::new(|| Box::pin(async { HealthStatus::degraded("something broke") })),
        )
        .await;

    let checks = health.check_all().await;
    assert_eq!(checks.len(), 1);
    assert!(!checks["test-plugin"].healthy);

    let all_ok = checks.values().all(|s| s.healthy);
    assert!(!all_ok);
}

// --- Plugin filtering ---

#[test]
fn filter_metrics_removes_disabled_plugins() {
    let reg = MetricsRegistryImpl::new();
    reg.counter_vec("folk_http_requests_total", "http requests", &[])
        .with_labels(&[])
        .inc();
    reg.counter_vec("folk_grpc_requests_total", "grpc requests", &[])
        .with_labels(&[])
        .inc();
    reg.counter_vec("folk_workers_total", "workers", &[])
        .with_labels(&[])
        .inc();

    let rendered = reg.render();
    assert!(rendered.contains("folk_http_requests_total"));
    assert!(rendered.contains("folk_grpc_requests_total"));
    assert!(rendered.contains("folk_workers_total"));

    // Filter out grpc metrics
    let handler = MetricsHandler {
        metrics: Some(reg.clone()),
        health: None,
        metrics_path: "/metrics".into(),
        health_path: "/health".into(),
        ready_path: "/ready".into(),
        disabled_prefixes: vec!["folk_grpc_"],
    };
    let _ = &handler;

    // Verify filtering logic directly
    let filtered = folk_plugin_metrics::handler::filter_metrics(&rendered, &["folk_grpc_"]);
    assert!(filtered.contains("folk_http_requests_total"));
    assert!(!filtered.contains("folk_grpc_requests_total"));
    assert!(filtered.contains("folk_workers_total"));
}

#[test]
fn filter_metrics_with_no_disabled_returns_all() {
    let reg = MetricsRegistryImpl::new();
    reg.counter_vec("folk_http_requests_total", "http requests", &[])
        .with_labels(&[])
        .inc();

    let rendered = reg.render();
    let filtered = folk_plugin_metrics::handler::filter_metrics(&rendered, &[]);
    assert_eq!(rendered, filtered);
}

// --- Config ---

#[test]
fn factory_creates_plugin() {
    use folk_plugin_metrics::folk_plugin_factory;

    let factory = folk_plugin_factory();
    let plugin = factory
        .create(serde_json::json!({}))
        .expect("factory should create plugin from empty config");
    assert_eq!(plugin.name(), "metrics");
}

#[test]
fn config_defaults() {
    use folk_plugin_metrics::MetricsConfig;

    let config = MetricsConfig::default();
    assert_eq!(config.listen.port(), 9090);
    assert_eq!(config.prefix, "folk");
    assert_eq!(config.metrics_path, "/metrics");
    assert_eq!(config.health_path, "/health");
    assert_eq!(config.ready_path, "/ready");
    assert!(config.plugins.is_empty());
}

#[test]
fn config_disabled_prefixes() {
    use folk_plugin_metrics::MetricsConfig;

    let mut config = MetricsConfig::default();
    config.plugins.insert("grpc".into(), false);
    config.plugins.insert("http".into(), true);

    let disabled = config.disabled_prefixes();
    assert!(disabled.contains(&"folk_grpc_"));
    assert!(!disabled.iter().any(|p| p.starts_with("folk_http_")));
}

#[test]
fn config_from_toml() {
    let json = serde_json::json!({
        "listen": "127.0.0.1:9191",
        "prefix": "myapp",
        "metrics_path": "/prom",
        "health_path": "/livez",
        "ready_path": "/readyz",
        "plugins": {
            "grpc": false,
            "http": true
        }
    });
    let config: folk_plugin_metrics::MetricsConfig =
        serde_json::from_value(json).expect("should parse config");
    assert_eq!(config.listen.port(), 9191);
    assert_eq!(config.prefix, "myapp");
    assert_eq!(config.metrics_path, "/prom");
    assert_eq!(config.health_path, "/livez");
    assert_eq!(config.ready_path, "/readyz");
    assert_eq!(config.plugins.get("grpc"), Some(&false));
    assert_eq!(config.plugins.get("http"), Some(&true));
}

// --- Custom metrics ---

#[test]
fn custom_counter_increment() {
    let reg = MetricsRegistryImpl::new();
    let defs = vec![CollectorDef {
        name: "app_requests_total".into(),
        collector_type: CollectorType::Counter,
        help: "Total app requests".into(),
        labels: vec!["method".into()],
        buckets: vec![],
    }];
    let custom = CustomMetrics::from_config(&defs, "folk", reg.as_ref());

    custom.increment("app_requests_total", &["GET"], 1).unwrap();
    custom.increment("app_requests_total", &["GET"], 4).unwrap();
    custom
        .increment("app_requests_total", &["POST"], 1)
        .unwrap();

    let output = reg.render();
    assert!(output.contains("folk_app_requests_total"));
    assert!(output.contains("method=\"GET\""));
    assert!(output.contains("method=\"POST\""));
}

#[test]
fn custom_gauge_set() {
    let reg = MetricsRegistryImpl::new();
    let defs = vec![CollectorDef {
        name: "active_connections".into(),
        collector_type: CollectorType::Gauge,
        help: "Active connections".into(),
        labels: vec![],
        buckets: vec![],
    }];
    let custom = CustomMetrics::from_config(&defs, "folk", reg.as_ref());

    custom.set("active_connections", &[], 42).unwrap();

    let output = reg.render();
    assert!(output.contains("folk_active_connections 42"));
}

#[test]
fn custom_histogram_observe() {
    let reg = MetricsRegistryImpl::new();
    let defs = vec![CollectorDef {
        name: "request_duration_seconds".into(),
        collector_type: CollectorType::Histogram,
        help: "Request duration".into(),
        labels: vec![],
        buckets: vec![],
    }];
    let custom = CustomMetrics::from_config(&defs, "folk", reg.as_ref());

    custom
        .observe("request_duration_seconds", &[], 0.123)
        .unwrap();

    let output = reg.render();
    assert!(output.contains("folk_request_duration_seconds"));
}

#[test]
fn custom_metric_not_found_error() {
    let reg = MetricsRegistryImpl::new();
    let custom = CustomMetrics::from_config(&[], "folk", reg.as_ref());

    let err = custom.increment("nonexistent", &[], 1);
    assert!(err.is_err());
    assert!(err.unwrap_err().to_string().contains("not found"));
}

#[test]
fn custom_metric_label_mismatch_error() {
    let reg = MetricsRegistryImpl::new();
    let defs = vec![CollectorDef {
        name: "my_counter".into(),
        collector_type: CollectorType::Counter,
        help: "test".into(),
        labels: vec!["a".into(), "b".into()],
        buckets: vec![],
    }];
    let custom = CustomMetrics::from_config(&defs, "folk", reg.as_ref());

    let err = custom.increment("my_counter", &["only_one"], 1);
    assert!(err.is_err());
    assert!(err.unwrap_err().to_string().contains("expected 2 labels"));
}

#[test]
fn custom_metric_empty_prefix() {
    let reg = MetricsRegistryImpl::new();
    let defs = vec![CollectorDef {
        name: "raw_counter".into(),
        collector_type: CollectorType::Counter,
        help: "no prefix".into(),
        labels: vec![],
        buckets: vec![],
    }];
    let custom = CustomMetrics::from_config(&defs, "", reg.as_ref());

    custom.increment("raw_counter", &[], 1).unwrap();

    let output = reg.render();
    assert!(output.contains("raw_counter"));
    // Should NOT have a leading underscore
    assert!(!output.contains("_raw_counter"));
}

#[test]
fn config_with_collectors() {
    let json = serde_json::json!({
        "collectors": [
            {
                "name": "app_requests_total",
                "type": "counter",
                "help": "Total requests",
                "labels": ["method", "endpoint"]
            },
            {
                "name": "request_duration_seconds",
                "type": "histogram",
                "help": "Request duration",
                "buckets": [0.01, 0.05, 0.1, 0.5, 1.0]
            },
            {
                "name": "queue_depth",
                "type": "gauge",
                "help": "Current queue depth"
            }
        ]
    });
    let config: folk_plugin_metrics::MetricsConfig =
        serde_json::from_value(json).expect("should parse config with collectors");
    assert_eq!(config.collectors.len(), 3);
    assert_eq!(config.collectors[0].name, "app_requests_total");
    assert_eq!(config.collectors[0].collector_type, CollectorType::Counter);
    assert_eq!(config.collectors[0].labels, vec!["method", "endpoint"]);
    assert_eq!(
        config.collectors[1].collector_type,
        CollectorType::Histogram
    );
    assert_eq!(
        config.collectors[1].buckets,
        vec![0.01, 0.05, 0.1, 0.5, 1.0]
    );
    assert_eq!(config.collectors[2].collector_type, CollectorType::Gauge);
}

#[test]
fn factory_accepts_empty_config() {
    use folk_plugin_metrics::folk_plugin_factory;
    assert!(
        folk_plugin_factory().create(serde_json::json!({})).is_ok(),
        "absent [metrics] section must not crash the server"
    );
}
