//! Metrics domain types.
//!
//! This module owns the shared metrics state and supporting summaries while the
//! Prometheus HTTP exporter continues to migrate out of `src/api/metrics.rs`.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Mutex;

pub(crate) const DURATION_BUCKETS_SECONDS: [f64; 15] = [
    0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 20.0, 30.0,
];

#[derive(Debug, Clone, Default)]
pub(crate) struct DurationSummary {
    pub(crate) count: u64,
    pub(crate) sum_seconds: f64,
    pub(crate) min_seconds: Option<f64>,
    pub(crate) max_seconds: Option<f64>,
    pub(crate) buckets: [u64; DURATION_BUCKETS_SECONDS.len()],
}

impl DurationSummary {
    pub(crate) fn record(&mut self, duration_seconds: f64) {
        let duration_seconds = duration_seconds.max(0.0);
        self.count += 1;
        self.sum_seconds += duration_seconds;
        self.min_seconds = Some(
            self.min_seconds
                .map(|value| value.min(duration_seconds))
                .unwrap_or(duration_seconds),
        );
        self.max_seconds = Some(
            self.max_seconds
                .map(|value| value.max(duration_seconds))
                .unwrap_or(duration_seconds),
        );

        for (index, upper_bound) in DURATION_BUCKETS_SECONDS.iter().enumerate() {
            if duration_seconds <= *upper_bound {
                self.buckets[index] += 1;
            }
        }
    }

    pub(crate) fn merge(&mut self, other: &DurationSummary) {
        self.count += other.count;
        self.sum_seconds += other.sum_seconds;

        if let Some(value) = other.min_seconds {
            self.min_seconds = Some(
                self.min_seconds
                    .map(|current| current.min(value))
                    .unwrap_or(value),
            );
        }

        if let Some(value) = other.max_seconds {
            self.max_seconds = Some(
                self.max_seconds
                    .map(|current| current.max(value))
                    .unwrap_or(value),
            );
        }

        for (index, bucket) in other.buckets.iter().enumerate() {
            self.buckets[index] += bucket;
        }
    }
}

#[derive(Debug, Clone, Default)]
pub(crate) struct ValueSummary {
    pub(crate) count: u64,
    pub(crate) sum: f64,
    pub(crate) min: Option<f64>,
    pub(crate) max: Option<f64>,
}

impl ValueSummary {
    pub(crate) fn record_u64(&mut self, value: u64) {
        let value: f64 = value as f64;
        self.count += 1;
        self.sum += value;
        self.min = Some(self.min.map(|current| current.min(value)).unwrap_or(value));
        self.max = Some(self.max.map(|current| current.max(value)).unwrap_or(value));
    }
}

#[derive(Debug, Clone, Default)]
pub(crate) struct HttpMetric {
    pub(crate) total: u64,
    pub(crate) duration: DurationSummary,
    pub(crate) request_bytes: ValueSummary,
    pub(crate) response_bytes: ValueSummary,
}

#[derive(Debug, Clone, Default)]
pub(crate) struct ManagementMetric {
    pub(crate) total: u64,
    pub(crate) duration: DurationSummary,
}

#[derive(Debug, Clone, Default)]
pub(crate) struct GatewayOperationMetric {
    pub(crate) total: u64,
    pub(crate) duration: DurationSummary,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ClusterProbeMetric {
    pub up: bool,
    pub latency_ms: Option<f64>,
    pub download_bytes_per_sec: Option<f64>,
}

#[derive(Debug, Clone, Default)]
pub(crate) struct HttpRouteMetric {
    pub(crate) in_flight: u64,
    pub(crate) max_in_flight: u64,
    pub(crate) handler_errors_total: u64,
}

#[derive(Default)]
pub struct MetricsState {
    pub(crate) http: Mutex<HashMap<(String, String, String), HttpMetric>>,
    pub(crate) http_status: Mutex<HashMap<(String, String, u16), HttpMetric>>,
    pub(crate) http_client: Mutex<HashMap<(String, String, String, String), HttpMetric>>,
    pub(crate) http_route: Mutex<HashMap<(String, String), HttpRouteMetric>>,
    pub(crate) management: Mutex<HashMap<(String, String), ManagementMetric>>,
    pub(crate) gateway_operation:
        Mutex<HashMap<(String, String, String, String, String, String), GatewayOperationMetric>>,
    pub(crate) gateway_operation_detailed: Mutex<
        HashMap<
            (
                String,
                String,
                String,
                String,
                String,
                u16,
                String,
                String,
                String,
                String,
            ),
            GatewayOperationMetric,
        >,
    >,
    pub(crate) cluster: Mutex<HashMap<String, ClusterProbeMetric>>,
    pub(crate) gateway_postgres_backend: Mutex<HashMap<(String, String), u64>>,
    pub(crate) deadpool_fallback: Mutex<HashMap<(String, String), u64>>,
    pub(crate) gateway_backend_unavailable: Mutex<HashMap<(String, String), u64>>,
    pub(crate) deferred_events: Mutex<HashMap<(String, String), u64>>,
    /// Gateway insert window: label -> count (e.g. `flush_single`, `flush_bulk`, `fallback_queue_full`).
    pub(crate) gateway_insert_window: Mutex<HashMap<String, u64>>,
    /// Gateway insert phase durations keyed by phase label.
    pub(crate) gateway_insert_phase_duration: Mutex<HashMap<String, DurationSummary>>,
    /// Row-level insert window counters: label -> total rows (e.g. `rows_submitted`, `rows_path_bulk`).
    pub(crate) gateway_insert_window_row_counts: Mutex<HashMap<String, u64>>,
    /// Batch size histogram: rows per `insert_rows_bulk` call (count/sum/min/max).
    pub(crate) gateway_insert_window_batch_size: Mutex<ValueSummary>,
    /// Queue depth observations recorded at each worker flush cycle.
    pub(crate) gateway_insert_window_queue_depth: Mutex<ValueSummary>,
}
