use model::{meta::CollectionMeta, report::PipelineReport};
use moka::sync::Cache;
use tokio::time::Instant;

use crate::{
    reports::{SinkReport, SourceReport},
    state::ApiState,
};
use axum::extract::State;
use lazy_static::*;
use log::*;
use prometheus::*;

const METRICS_COUNTER_NAME: &str = "metrics_counter";

lazy_static! {
    static ref METRICS_COUNTER: Cache<String, i32> = Cache::new(10);
    static ref REGISTRY: Registry = Registry::new();
    static ref ENCODER: TextEncoder = TextEncoder::new();
    pub static ref ROUTER_COLLECTION_WITH_PATH: IntCounterVec =
        register_int_counter_vec_with_registry!(
            opts!(
                "qdrant_api_collection_actions_total",
                "collections-api's request count with action and method"
            ),
            &["collection_name", "action", "method"],
            REGISTRY,
        )
        .unwrap();
    pub static ref ROUTER_COLLECTION_WITH_PATH_HISTOGRAM: HistogramVec =
        register_histogram_vec_with_registry!(
            histogram_opts!(
                "qdrant_api_collection_actions_duration_seconds",
                "collections-api's request timing with action and method"
            ),
            &["collection_name", "action", "method"],
            REGISTRY,
        )
        .unwrap();
    pub static ref INTERFACE_PERFORMANCE_HISTOGRAM: HistogramVec =
        register_histogram_vec_with_registry!(
            histogram_opts!(
                "qdrant_api_interface_performance_duration_seconds",
                "qdrant-api's request timing with interface"
            ),
            &["interface", "phase"],
            REGISTRY,
        )
        .unwrap();
    pub static ref REPORTS_TOTALS: GaugeVec = register_gauge_vec_with_registry!(
        opts!("qdrant_api_reports_totals", "qdrant-api's reports"),
        &["phase", "target"],
        REGISTRY,
    )
    .unwrap();
    pub static ref ERRORS_REPORT: IntCounterVec = register_int_counter_vec_with_registry!(
        opts!("qdrant_api_errors_total", "errors in qdrant-on-tidb"),
        &["category", "level"],
        REGISTRY,
    )
    .unwrap();
}

pub(crate) fn since(start: &Instant) -> f64 {
    start.elapsed().as_secs_f64()
}

fn reset_metrics() {
    if METRICS_COUNTER.get(METRICS_COUNTER_NAME).is_none() {
        METRICS_COUNTER.insert(METRICS_COUNTER_NAME.into(), 1);
        return;
    }

    let counter = METRICS_COUNTER
        .get(METRICS_COUNTER_NAME)
        .unwrap_or_default();
    if counter < 100 {
        METRICS_COUNTER.insert(METRICS_COUNTER_NAME.into(), counter + 1);
        return;
    }

    ROUTER_COLLECTION_WITH_PATH.reset();
    ROUTER_COLLECTION_WITH_PATH_HISTOGRAM.reset();
    INTERFACE_PERFORMANCE_HISTOGRAM.reset();
    REPORTS_TOTALS.reset();
    ERRORS_REPORT.reset();

    METRICS_COUNTER.insert(METRICS_COUNTER_NAME.into(), 1);
}

pub(crate) async fn collection_metrics(State(state): State<ApiState>) -> String {
    pipeline_report_metrics(state.clone()).await;
    tenant_count_metrics(state).await;

    let mut buffer = vec![];
    let metric_families = REGISTRY.gather();
    ENCODER.encode(&metric_families, &mut buffer).unwrap();

    reset_metrics();

    // Output to the standard output.
    String::from_utf8(buffer).unwrap()
}

pub(crate) async fn pipeline_report_metrics(state: ApiState) {
    let meta_db = match state.get_meta_db().await {
        Some(db) => db,
        None => return,
    };
    let report = match PipelineReport::find_offset(&meta_db, 0, 1).await {
        Ok(report) => report,
        Err(e) => {
            error!("Failed to get pipeline report: {}", e);
            return;
        }
    };
    let report = report.first().unwrap();

    let collections: SourceReport = serde_json::from_str(&report.collection_source).unwrap();
    let documents: SourceReport = serde_json::from_str(&report.documents_source).unwrap();
    let index: SinkReport = serde_json::from_str(&report.index_sink).unwrap();
    REPORTS_TOTALS
        .with_label_values(&["collections", "successes"])
        .set(collections.successes as f64);
    REPORTS_TOTALS
        .with_label_values(&["documents", "successes"])
        .set(documents.successes as f64);
    REPORTS_TOTALS
        .with_label_values(&["index", "successes"])
        .set(index.successes as f64);
    REPORTS_TOTALS
        .with_label_values(&["index", "deletes"])
        .set(index.deletes as f64);
    REPORTS_TOTALS
        .with_label_values(&["total_cost", ""])
        .set(report.cost_millis as f64);
}

pub(crate) async fn tenant_count_metrics(state: ApiState) {
    let meta_db = match state.get_meta_db().await {
        Some(db) => db,
        None => return,
    };
    let count = CollectionMeta::count(&meta_db).await.unwrap();
    REPORTS_TOTALS
        .with_label_values(&["tenants", "collections"])
        .set(count.0 as f64);
    REPORTS_TOTALS
        .with_label_values(&["tenants", "tokens"])
        .set(count.1 as f64);
}

pub(crate) fn error_metrics(category: &str, level: &str) {
    ERRORS_REPORT.with_label_values(&[category, level]).inc();
}
