//! Time-Series Store - Metrics and analytics storage.
//!
//! Optimized for high-ingestion rate time-stamped data.

use crate::error::DatabaseError;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};

/// A single data point in time series.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DataPoint {
    /// Timestamp (milliseconds since epoch).
    pub timestamp: u64,
    /// Numeric value.
    pub value: f64,
    /// Optional tags/labels.
    pub tags: HashMap<String, String>,
}

impl DataPoint {
    /// Creates a new data point.
    pub fn new(timestamp: u64, value: f64) -> Self {
        Self {
            timestamp,
            value,
            tags: HashMap::new(),
        }
    }

    /// Creates a data point at current time.
    pub fn now(value: f64) -> Self {
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;
        Self::new(timestamp, value)
    }

    /// Adds a tag.
    pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.tags.insert(key.into(), value.into());
        self
    }

    /// Adds multiple tags.
    pub fn with_tags(mut self, tags: HashMap<String, String>) -> Self {
        self.tags.extend(tags);
        self
    }
}

/// A metric time series.
#[derive(Debug)]
pub struct Metric {
    /// Metric name.
    pub name: String,
    /// Data points ordered by timestamp.
    data: RwLock<BTreeMap<u64, f64>>,
    /// Tags for this metric.
    pub tags: HashMap<String, String>,
    /// Retention period (ms), 0 = infinite.
    pub retention_ms: u64,
}

impl Metric {
    /// Creates a new metric.
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            data: RwLock::new(BTreeMap::new()),
            tags: HashMap::new(),
            retention_ms: 0,
        }
    }

    /// Sets retention period.
    pub fn with_retention(mut self, retention_ms: u64) -> Self {
        self.retention_ms = retention_ms;
        self
    }

    /// Adds a tag.
    pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.tags.insert(key.into(), value.into());
        self
    }

    /// Records a data point.
    pub fn record(&self, point: DataPoint) {
        self.data.write().insert(point.timestamp, point.value);
    }

    /// Records current value.
    pub fn record_now(&self, value: f64) {
        self.record(DataPoint::now(value));
    }

    /// Increments by 1.
    pub fn increment(&self) {
        self.add(1.0);
    }

    /// Adds to the current value.
    pub fn add(&self, delta: f64) {
        let point = DataPoint::now(delta);
        let mut data = self.data.write();
        let current = data.values().last().copied().unwrap_or(0.0);
        data.insert(point.timestamp, current + delta);
    }

    /// Gets data points in a time range.
    pub fn range(&self, start: u64, end: u64) -> Vec<DataPoint> {
        self.data
            .read()
            .range(start..=end)
            .map(|(&timestamp, &value)| DataPoint::new(timestamp, value))
            .collect()
    }

    /// Gets the latest value.
    pub fn latest(&self) -> Option<DataPoint> {
        self.data
            .read()
            .iter()
            .last()
            .map(|(&timestamp, &value)| DataPoint::new(timestamp, value))
    }

    /// Calculates average in a time range.
    pub fn avg(&self, start: u64, end: u64) -> Option<f64> {
        let data = self.data.read();
        let values: Vec<f64> = data.range(start..=end).map(|(_, &v)| v).collect();
        if values.is_empty() {
            None
        } else {
            Some(values.iter().sum::<f64>() / values.len() as f64)
        }
    }

    /// Calculates min in a time range.
    pub fn min(&self, start: u64, end: u64) -> Option<f64> {
        self.data
            .read()
            .range(start..=end)
            .map(|(_, &v)| v)
            .min_by(|a, b| a.partial_cmp(b).unwrap())
    }

    /// Calculates max in a time range.
    pub fn max(&self, start: u64, end: u64) -> Option<f64> {
        self.data
            .read()
            .range(start..=end)
            .map(|(_, &v)| v)
            .max_by(|a, b| a.partial_cmp(b).unwrap())
    }

    /// Calculates sum in a time range.
    pub fn sum(&self, start: u64, end: u64) -> f64 {
        self.data
            .read()
            .range(start..=end)
            .map(|(_, &v)| v)
            .sum()
    }

    /// Counts data points in a time range.
    pub fn count(&self, start: u64, end: u64) -> usize {
        self.data.read().range(start..=end).count()
    }

    /// Returns total data points.
    pub fn len(&self) -> usize {
        self.data.read().len()
    }

    /// Checks if empty.
    pub fn is_empty(&self) -> bool {
        self.data.read().is_empty()
    }

    /// Evicts data older than retention period.
    pub fn evict_old(&self) -> usize {
        if self.retention_ms == 0 {
            return 0;
        }

        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as u64;

        let cutoff = now.saturating_sub(self.retention_ms);

        let mut data = self.data.write();
        let before = data.len();
        data.retain(|&ts, _| ts >= cutoff);
        before - data.len()
    }

    /// Downsamples data to reduce storage.
    pub fn downsample(&self, interval_ms: u64, agg: Aggregation) -> Vec<DataPoint> {
        let data = self.data.read();
        if data.is_empty() {
            return Vec::new();
        }

        let first_ts = *data.keys().next().unwrap();
        let last_ts = *data.keys().last().unwrap();

        let mut result = Vec::new();
        let mut bucket_start = first_ts;

        while bucket_start <= last_ts {
            let bucket_end = bucket_start + interval_ms;
            let values: Vec<f64> = data
                .range(bucket_start..bucket_end)
                .map(|(_, &v)| v)
                .collect();

            if !values.is_empty() {
                let value = match agg {
                    Aggregation::Avg => values.iter().sum::<f64>() / values.len() as f64,
                    Aggregation::Sum => values.iter().sum(),
                    Aggregation::Min => values
                        .iter()
                        .copied()
                        .min_by(|a, b| a.partial_cmp(b).unwrap())
                        .unwrap(),
                    Aggregation::Max => values
                        .iter()
                        .copied()
                        .max_by(|a, b| a.partial_cmp(b).unwrap())
                        .unwrap(),
                    Aggregation::First => values[0],
                    Aggregation::Last => *values.last().unwrap(),
                    Aggregation::Count => values.len() as f64,
                };
                result.push(DataPoint::new(bucket_start, value));
            }

            bucket_start = bucket_end;
        }

        result
    }
}

/// Aggregation function for downsampling.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Aggregation {
    Avg,
    Sum,
    Min,
    Max,
    First,
    Last,
    Count,
}

/// Time series store managing multiple metrics.
#[derive(Debug)]
pub struct TimeSeries {
    /// Metric name.
    pub name: String,
    /// Series data (tag-set -> points).
    series: RwLock<HashMap<String, Vec<DataPoint>>>,
}

impl TimeSeries {
    /// Creates a new time series.
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            series: RwLock::new(HashMap::new()),
        }
    }

    /// Adds a data point.
    pub fn add(&self, tags: &HashMap<String, String>, point: DataPoint) {
        let key = Self::tags_to_key(tags);
        self.series
            .write()
            .entry(key)
            .or_insert_with(Vec::new)
            .push(point);
    }

    /// Queries data points.
    pub fn query(
        &self,
        tags: Option<&HashMap<String, String>>,
        start: u64,
        end: u64,
    ) -> Vec<DataPoint> {
        let series = self.series.read();

        if let Some(tags) = tags {
            let key = Self::tags_to_key(tags);
            series
                .get(&key)
                .map(|points| {
                    points
                        .iter()
                        .filter(|p| p.timestamp >= start && p.timestamp <= end)
                        .cloned()
                        .collect()
                })
                .unwrap_or_default()
        } else {
            series
                .values()
                .flat_map(|points| {
                    points
                        .iter()
                        .filter(|p| p.timestamp >= start && p.timestamp <= end)
                        .cloned()
                })
                .collect()
        }
    }

    fn tags_to_key(tags: &HashMap<String, String>) -> String {
        let mut pairs: Vec<_> = tags.iter().collect();
        pairs.sort_by_key(|(k, _)| *k);
        pairs
            .iter()
            .map(|(k, v)| format!("{}={}", k, v))
            .collect::<Vec<_>>()
            .join(",")
    }
}

/// Time series store.
pub struct TimeSeriesStore {
    /// Metrics by name.
    metrics: RwLock<HashMap<String, Metric>>,
    /// Time series by name.
    series: RwLock<HashMap<String, TimeSeries>>,
}

impl TimeSeriesStore {
    /// Creates a new time series store.
    pub fn new() -> Self {
        Self {
            metrics: RwLock::new(HashMap::new()),
            series: RwLock::new(HashMap::new()),
        }
    }

    /// Creates or gets a metric.
    pub fn metric(&self, name: &str) -> Result<(), DatabaseError> {
        let mut metrics = self.metrics.write();
        if !metrics.contains_key(name) {
            metrics.insert(name.to_string(), Metric::new(name));
        }
        Ok(())
    }

    /// Records a metric value.
    pub fn record(&self, name: &str, value: f64) -> Result<(), DatabaseError> {
        let metrics = self.metrics.read();
        if let Some(metric) = metrics.get(name) {
            metric.record_now(value);
            Ok(())
        } else {
            drop(metrics);
            self.metric(name)?;
            self.record(name, value)
        }
    }

    /// Records a data point with timestamp.
    pub fn record_point(&self, name: &str, point: DataPoint) -> Result<(), DatabaseError> {
        let metrics = self.metrics.read();
        if let Some(metric) = metrics.get(name) {
            metric.record(point);
            Ok(())
        } else {
            drop(metrics);
            self.metric(name)?;
            self.record_point(name, point)
        }
    }

    /// Queries metric data.
    pub fn query(&self, name: &str, start: u64, end: u64) -> Result<Vec<DataPoint>, DatabaseError> {
        let metrics = self.metrics.read();
        let metric = metrics
            .get(name)
            .ok_or_else(|| DatabaseError::NotFound(name.to_string()))?;
        Ok(metric.range(start, end))
    }

    /// Gets latest metric value.
    pub fn latest(&self, name: &str) -> Result<Option<DataPoint>, DatabaseError> {
        let metrics = self.metrics.read();
        let metric = metrics
            .get(name)
            .ok_or_else(|| DatabaseError::NotFound(name.to_string()))?;
        Ok(metric.latest())
    }

    /// Aggregates metric data.
    pub fn aggregate(
        &self,
        name: &str,
        start: u64,
        end: u64,
        agg: Aggregation,
    ) -> Result<Option<f64>, DatabaseError> {
        let metrics = self.metrics.read();
        let metric = metrics
            .get(name)
            .ok_or_else(|| DatabaseError::NotFound(name.to_string()))?;

        let result = match agg {
            Aggregation::Avg => metric.avg(start, end),
            Aggregation::Sum => Some(metric.sum(start, end)),
            Aggregation::Min => metric.min(start, end),
            Aggregation::Max => metric.max(start, end),
            Aggregation::Count => Some(metric.count(start, end) as f64),
            _ => None,
        };

        Ok(result)
    }

    /// Lists all metrics.
    pub fn list_metrics(&self) -> Vec<String> {
        self.metrics.read().keys().cloned().collect()
    }

    /// Deletes a metric.
    pub fn delete_metric(&self, name: &str) -> Result<bool, DatabaseError> {
        Ok(self.metrics.write().remove(name).is_some())
    }
}

impl Default for TimeSeriesStore {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_data_point() {
        let point = DataPoint::new(1000, 42.5)
            .with_tag("host", "server1")
            .with_tag("region", "us-east");

        assert_eq!(point.timestamp, 1000);
        assert_eq!(point.value, 42.5);
        assert_eq!(point.tags.get("host"), Some(&"server1".to_string()));
    }

    #[test]
    fn test_metric_record() {
        let metric = Metric::new("cpu_usage");

        metric.record(DataPoint::new(1000, 50.0));
        metric.record(DataPoint::new(2000, 60.0));
        metric.record(DataPoint::new(3000, 55.0));

        let latest = metric.latest().unwrap();
        assert_eq!(latest.timestamp, 3000);
        assert_eq!(latest.value, 55.0);
    }

    #[test]
    fn test_metric_range() {
        let metric = Metric::new("requests");

        metric.record(DataPoint::new(1000, 10.0));
        metric.record(DataPoint::new(2000, 20.0));
        metric.record(DataPoint::new(3000, 30.0));
        metric.record(DataPoint::new(4000, 40.0));

        let range = metric.range(1500, 3500);
        assert_eq!(range.len(), 2);
        assert_eq!(range[0].value, 20.0);
        assert_eq!(range[1].value, 30.0);
    }

    #[test]
    fn test_metric_aggregations() {
        let metric = Metric::new("values");

        metric.record(DataPoint::new(1000, 10.0));
        metric.record(DataPoint::new(2000, 20.0));
        metric.record(DataPoint::new(3000, 30.0));

        assert_eq!(metric.avg(0, 5000), Some(20.0));
        assert_eq!(metric.min(0, 5000), Some(10.0));
        assert_eq!(metric.max(0, 5000), Some(30.0));
        assert_eq!(metric.sum(0, 5000), 60.0);
        assert_eq!(metric.count(0, 5000), 3);
    }

    #[test]
    fn test_metric_downsample() {
        let metric = Metric::new("samples");

        // Add 10 points over 1000ms
        for i in 0..10 {
            metric.record(DataPoint::new(i * 100, i as f64));
        }

        // Downsample to 500ms buckets with avg
        let downsampled = metric.downsample(500, Aggregation::Avg);
        assert_eq!(downsampled.len(), 2);
    }

    #[test]
    fn test_timeseries_store() {
        let store = TimeSeriesStore::new();

        store.record("cpu", 50.0).unwrap();
        store.record("cpu", 60.0).unwrap();
        store.record("memory", 1024.0).unwrap();

        let metrics = store.list_metrics();
        assert!(metrics.contains(&"cpu".to_string()));
        assert!(metrics.contains(&"memory".to_string()));

        let latest = store.latest("cpu").unwrap().unwrap();
        assert_eq!(latest.value, 60.0);
    }
}
