//! Aggregation framework: collectors, result types, and aggregation definitions.
//!
//! See [[aggregations]] and [[feature-aggregations-v010|milestone-3]].

pub mod parser;
pub mod metric;
pub mod bucket;
pub mod hll;
pub mod tdigest;

use std::collections::HashMap;
use luci_core::DocId;
use crate::segment::reader::SegmentReader;

// --- Result types ---

/// Top-level aggregation result.
#[derive(Clone, Debug)]
pub enum AggResult {
    Metric(MetricResult),
    Bucket(BucketResult),
    Hits(HitsResult),
}

/// Result from a metric aggregation (single numeric value).
#[derive(Clone, Debug)]
pub struct MetricResult {
    pub value: Option<f64>,
    /// For stats agg: additional fields
    pub extra: HashMap<String, f64>,
}

impl MetricResult {
    pub fn single(value: Option<f64>) -> Self {
        Self { value, extra: HashMap::new() }
    }

    pub fn stats(count: u64, min: f64, max: f64, avg: f64, sum: f64) -> Self {
        let mut extra = HashMap::new();
        extra.insert("count".into(), count as f64);
        extra.insert("min".into(), min);
        extra.insert("max".into(), max);
        extra.insert("sum".into(), sum);
        Self { value: Some(avg), extra }
    }
}

/// Result from a bucket aggregation.
#[derive(Clone, Debug)]
pub struct BucketResult {
    pub buckets: Vec<Bucket>,
}

/// A single bucket in a bucket aggregation result.
#[derive(Clone, Debug)]
pub struct Bucket {
    pub key: BucketKey,
    pub doc_count: u64,
    pub sub_aggs: HashMap<String, AggResult>,
}

/// The key identifying a bucket.
#[derive(Clone, Debug)]
pub enum BucketKey {
    String(String),
    Number(f64),
    Range { from: Option<f64>, to: Option<f64> },
}

/// Result from a top_hits aggregation.
#[derive(Clone, Debug)]
pub struct HitsResult {
    pub hits: Vec<serde_json::Value>,
}

// --- JSON serialization ---

impl AggResult {
    pub fn to_json(&self) -> serde_json::Value {
        match self {
            AggResult::Metric(m) => m.to_json(),
            AggResult::Bucket(b) => b.to_json(),
            AggResult::Hits(h) => h.to_json(),
        }
    }
}

impl HitsResult {
    pub fn to_json(&self) -> serde_json::Value {
        serde_json::json!({"hits": {"hits": self.hits}})
    }
}

impl MetricResult {
    pub fn to_json(&self) -> serde_json::Value {
        if self.extra.is_empty() {
            serde_json::json!({"value": self.value})
        } else {
            let mut obj = serde_json::Map::new();
            if let Some(v) = self.value {
                obj.insert("value".into(), serde_json::json!(v));
            }
            for (k, v) in &self.extra {
                obj.insert(k.clone(), serde_json::json!(v));
            }
            serde_json::Value::Object(obj)
        }
    }
}

impl BucketResult {
    pub fn to_json(&self) -> serde_json::Value {
        let buckets: Vec<serde_json::Value> = self.buckets.iter().map(|b| b.to_json()).collect();
        serde_json::json!({"buckets": buckets})
    }
}

impl Bucket {
    pub fn to_json(&self) -> serde_json::Value {
        let mut obj = serde_json::Map::new();
        match &self.key {
            BucketKey::String(s) => { obj.insert("key".into(), serde_json::json!(s)); }
            BucketKey::Number(n) => { obj.insert("key".into(), serde_json::json!(n)); }
            BucketKey::Range { from, to } => {
                if let Some(f) = from { obj.insert("from".into(), serde_json::json!(f)); }
                if let Some(t) = to { obj.insert("to".into(), serde_json::json!(t)); }
            }
        }
        obj.insert("doc_count".into(), serde_json::json!(self.doc_count));
        for (name, result) in &self.sub_aggs {
            obj.insert(name.clone(), result.to_json());
        }
        serde_json::Value::Object(obj)
    }
}

// --- Collector traits ---

/// Creates per-segment aggregation collectors and merges their results.
pub trait AggCollectorFactory: Send + Sync {
    /// Create a collector for a specific segment.
    fn create_collector(&self, reader: &SegmentReader) -> Box<dyn AggCollector>;

    /// Merge results from multiple segments.
    fn merge_results(&self, results: Vec<AggResult>) -> AggResult;
}

/// Per-segment aggregation collector. Called once per matching document.
pub trait AggCollector: Send {
    /// Collect a matching document.
    fn collect(&mut self, doc_id: DocId);

    /// Produce the segment-level result.
    fn finish(self: Box<Self>) -> AggResult;
}

// --- Aggregation definition ---

/// Parsed aggregation definition from JSON.
#[derive(Clone, Debug)]
pub struct AggDef {
    pub name: String,
    pub agg_type: AggType,
    pub sub_aggs: Vec<AggDef>,
}

/// Supported aggregation types.
#[derive(Clone, Debug)]
pub enum AggType {
    Avg { field: String },
    Sum { field: String },
    Min { field: String },
    Max { field: String },
    ValueCount { field: String },
    Stats { field: String },
    ExtendedStats { field: String },
    Terms { field: String, size: usize },
    Range { field: String, ranges: Vec<RangeDef> },
    Histogram { field: String, interval: f64 },
    Filter { query: crate::query::ast::QueryAST },
    Filters { filters: Vec<(String, crate::query::ast::QueryAST)> },
    DateRange { field: String, ranges: Vec<RangeDef> },
    GeoBounds { field: String },
    GeoCentroid { field: String },
    Cardinality { field: String, precision_threshold: u32 },
    Percentiles { field: String, percents: Vec<f64>, compression: f64 },
    DateHistogram { field: String, interval: DateInterval },
    TopHits { size: usize },
    GeohashGrid { field: String, precision: usize, size: usize },
    Nested { path: String },
    ReverseNested,
}

/// Date histogram interval.
#[derive(Clone, Debug)]
pub enum DateInterval {
    /// Fixed interval in milliseconds (e.g., "1d" = 86400000ms).
    Fixed(f64),
    /// Calendar interval: month, quarter, year (variable length).
    Calendar(CalendarInterval),
}

/// Calendar-aware intervals.
#[derive(Clone, Debug)]
pub enum CalendarInterval {
    Minute,
    Hour,
    Day,
    Week,
    Month,
    Quarter,
    Year,
}

/// A range definition for range aggregation.
#[derive(Clone, Debug)]
pub struct RangeDef {
    pub key: Option<String>,
    pub from: Option<f64>,
    pub to: Option<f64>,
}

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

    #[test]
    fn metric_result_json() {
        let r = MetricResult::single(Some(3.14));
        let j = r.to_json();
        assert_eq!(j["value"], 3.14);
    }

    #[test]
    fn metric_result_null() {
        let r = MetricResult::single(None);
        let j = r.to_json();
        assert!(j["value"].is_null());
    }

    #[test]
    fn stats_result_json() {
        let r = MetricResult::stats(10, 1.0, 100.0, 50.5, 505.0);
        let j = r.to_json();
        assert_eq!(j["value"], 50.5);
        assert_eq!(j["count"], 10.0);
        assert_eq!(j["min"], 1.0);
    }

    #[test]
    fn bucket_result_json() {
        let r = BucketResult {
            buckets: vec![
                Bucket {
                    key: BucketKey::String("tech".into()),
                    doc_count: 42,
                    sub_aggs: HashMap::new(),
                },
            ],
        };
        let j = r.to_json();
        assert_eq!(j["buckets"][0]["key"], "tech");
        assert_eq!(j["buckets"][0]["doc_count"], 42);
    }

    #[test]
    fn nested_sub_agg_json() {
        let r = BucketResult {
            buckets: vec![Bucket {
                key: BucketKey::String("a".into()),
                doc_count: 10,
                sub_aggs: {
                    let mut m = HashMap::new();
                    m.insert("avg_price".into(), AggResult::Metric(MetricResult::single(Some(25.0))));
                    m
                },
            }],
        };
        let j = r.to_json();
        assert_eq!(j["buckets"][0]["avg_price"]["value"], 25.0);
    }

    #[test]
    fn range_key_json() {
        let b = Bucket {
            key: BucketKey::Range { from: Some(0.0), to: Some(100.0) },
            doc_count: 5,
            sub_aggs: HashMap::new(),
        };
        let j = b.to_json();
        assert_eq!(j["from"], 0.0);
        assert_eq!(j["to"], 100.0);
    }
}
