//! Metric and label types
//!
//! This module defines the core metric representation following the Prometheus data model.
//! A metric is uniquely identified by its label set.

use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

/// Label represents a key-value pair that identifies a dimension of a metric.
///
/// Labels are used to identify and filter time series. A set of labels uniquely
/// identifies a time series in the database.
///
/// # Examples
///
/// ```
/// use nightlight_types::Label;
///
/// let label = Label {
///     name: "method".to_string(),
///     value: "GET".to_string(),
/// };
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Label {
    /// Label name (e.g., "__name__", "job", "instance", "method")
    pub name: String,
    /// Label value
    pub value: String,
}

impl Label {
    /// Create a new label
    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            value: value.into(),
        }
    }

    /// Check if this is the special __name__ label (metric name)
    pub fn is_metric_name(&self) -> bool {
        self.name == "__name__"
    }
}

/// Sample represents a single data point in a time series.
///
/// A sample consists of a timestamp (milliseconds since Unix epoch) and a value.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Sample {
    /// Timestamp in milliseconds since Unix epoch
    pub timestamp: i64,
    /// Sample value (f64 as per Prometheus spec)
    pub value: f64,
}

impl Sample {
    /// Create a new sample
    pub fn new(timestamp: i64, value: f64) -> Self {
        Self { timestamp, value }
    }
}

/// Metric represents a time series identifier (labels only, no data points).
///
/// A metric is uniquely identified by its complete label set. This type is used
/// for indexing and lookups without carrying the full sample data.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Metric {
    /// Complete label set identifying this metric
    /// Must be sorted by label name for consistent fingerprinting
    pub labels: Vec<Label>,
}

impl Metric {
    /// Create a new metric with the given labels
    ///
    /// # Note
    ///
    /// Labels will be sorted by name to ensure consistent fingerprinting.
    pub fn new(mut labels: Vec<Label>) -> Self {
        // Sort labels by name for consistent fingerprinting
        labels.sort_by(|a, b| a.name.cmp(&b.name));
        Self { labels }
    }

    /// Get the metric name (value of the __name__ label)
    pub fn name(&self) -> Option<&str> {
        self.labels
            .iter()
            .find(|l| l.is_metric_name())
            .map(|l| l.value.as_str())
    }

    /// Get a label value by name
    pub fn get_label(&self, name: &str) -> Option<&str> {
        self.labels
            .iter()
            .find(|l| l.name == name)
            .map(|l| l.value.as_str())
    }

    /// Calculate a stable fingerprint hash for this metric.
    ///
    /// The fingerprint is used as a fast lookup key for time series.
    /// It must be stable across restarts and consistent across all nodes.
    ///
    /// # Algorithm
    ///
    /// We use a simple hash of the sorted label set. This is fast but has
    /// collision potential. For production use, consider using a more robust
    /// fingerprinting algorithm like xxHash or FarmHash.
    ///
    /// # TODO (S5)
    ///
    /// - Evaluate collision rate in production workloads
    /// - Consider switching to xxHash or FarmHash
    /// - Add collision detection and handling
    pub fn fingerprint(&self) -> u64 {
        let mut hasher = DefaultHasher::new();

        // Hash each label (already sorted in constructor)
        for label in &self.labels {
            label.name.hash(&mut hasher);
            label.value.hash(&mut hasher);
        }

        hasher.finish()
    }

    /// Check if this metric matches a set of label matchers
    ///
    /// # TODO (S4)
    ///
    /// Implement proper PromQL matcher semantics:
    /// - Exact match: label="value"
    /// - Regex match: label=~"regex"
    /// - Negative match: label!="value"
    /// - Negative regex: label!~"regex"
    pub fn matches(&self, _matchers: &[String]) -> bool {
        // Placeholder implementation
        // Will be implemented in S4 (PromQL engine)
        true
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_label_creation() {
        let label = Label::new("job", "api");
        assert_eq!(label.name, "job");
        assert_eq!(label.value, "api");
    }

    #[test]
    fn test_metric_name_label() {
        let label = Label::new("__name__", "http_requests_total");
        assert!(label.is_metric_name());
    }

    #[test]
    fn test_sample_creation() {
        let sample = Sample::new(1234567890000, 42.0);
        assert_eq!(sample.timestamp, 1234567890000);
        assert_eq!(sample.value, 42.0);
    }

    #[test]
    fn test_metric_creation() {
        let labels = vec![
            Label::new("__name__", "http_requests_total"),
            Label::new("method", "GET"),
            Label::new("status", "200"),
        ];
        let metric = Metric::new(labels);

        assert_eq!(metric.name(), Some("http_requests_total"));
        assert_eq!(metric.get_label("method"), Some("GET"));
        assert_eq!(metric.get_label("status"), Some("200"));
    }

    #[test]
    fn test_metric_fingerprint_stability() {
        // Same labels in different order should produce same fingerprint
        let labels1 = vec![
            Label::new("__name__", "test"),
            Label::new("a", "1"),
            Label::new("b", "2"),
        ];
        let labels2 = vec![
            Label::new("b", "2"),
            Label::new("__name__", "test"),
            Label::new("a", "1"),
        ];

        let metric1 = Metric::new(labels1);
        let metric2 = Metric::new(labels2);

        assert_eq!(metric1.fingerprint(), metric2.fingerprint());
    }

    #[test]
    fn test_metric_fingerprint_uniqueness() {
        // Different labels should produce different fingerprints
        let metric1 = Metric::new(vec![Label::new("__name__", "test1")]);
        let metric2 = Metric::new(vec![Label::new("__name__", "test2")]);

        // Note: This test could theoretically fail due to hash collisions,
        // but the probability is extremely low for different inputs
        assert_ne!(metric1.fingerprint(), metric2.fingerprint());
    }
}
