//! Aggregation JSON parser.
//!
//! See [[aggregations]] and [[feature-aggregations-v010#Step 4]].

use luci_core::{LuciError, Result};
use serde_json::Value;

use super::{AggDef, AggType, RangeDef};
use crate::query::parser::parse_query;

/// Parse the `"aggs"` section of a search request.
pub fn parse_aggs(json: &Value) -> Result<Vec<AggDef>> {
    let obj = match json.as_object() {
        Some(o) => o,
        None => return Err(LuciError::InvalidQuery("aggs must be an object".into())),
    };

    let mut defs = Vec::new();
    for (name, agg_val) in obj {
        defs.push(parse_single_agg(name, agg_val)?);
    }
    Ok(defs)
}

fn parse_single_agg(name: &str, val: &Value) -> Result<AggDef> {
    let obj = val.as_object().ok_or_else(|| {
        LuciError::InvalidQuery(format!("aggregation '{name}' must be an object"))
    })?;

    // Find the agg type key (not "aggs")
    let mut agg_type = None;
    let mut sub_aggs_val = None;

    for (key, v) in obj {
        match key.as_str() {
            "aggs" | "aggregations" => sub_aggs_val = Some(v),
            _ => {
                if agg_type.is_some() {
                    return Err(LuciError::InvalidQuery(format!(
                        "aggregation '{name}' has multiple type keys"
                    )));
                }
                agg_type = Some((key.as_str(), v));
            }
        }
    }

    let (type_key, type_val) = agg_type.ok_or_else(|| {
        LuciError::InvalidQuery(format!("aggregation '{name}' has no type"))
    })?;

    let at = parse_agg_type(type_key, type_val)?;

    let sub_aggs = match sub_aggs_val {
        Some(v) => parse_aggs(v)?,
        None => Vec::new(),
    };

    Ok(AggDef {
        name: name.to_string(),
        agg_type: at,
        sub_aggs,
    })
}

fn parse_agg_type(key: &str, val: &Value) -> Result<AggType> {
    let obj = val.as_object().ok_or_else(|| {
        LuciError::InvalidQuery(format!("aggregation type '{key}' must be an object"))
    })?;

    match key {
        "avg" => Ok(AggType::Avg { field: require_field(obj, key)? }),
        "sum" => Ok(AggType::Sum { field: require_field(obj, key)? }),
        "min" => Ok(AggType::Min { field: require_field(obj, key)? }),
        "max" => Ok(AggType::Max { field: require_field(obj, key)? }),
        "value_count" => Ok(AggType::ValueCount { field: require_field(obj, key)? }),
        "stats" => Ok(AggType::Stats { field: require_field(obj, key)? }),
        "extended_stats" => Ok(AggType::ExtendedStats { field: require_field(obj, key)? }),
        "terms" => {
            let field = require_field(obj, key)?;
            let size = obj.get("size").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
            Ok(AggType::Terms { field, size })
        }
        "range" => {
            let field = require_field(obj, key)?;
            let ranges_val = obj.get("ranges").and_then(|v| v.as_array())
                .ok_or_else(|| LuciError::InvalidQuery("range agg requires 'ranges' array".into()))?;
            let mut ranges = Vec::new();
            for r in ranges_val {
                let r_obj = r.as_object().ok_or_else(|| {
                    LuciError::InvalidQuery("range entry must be an object".into())
                })?;
                ranges.push(RangeDef {
                    key: r_obj.get("key").and_then(|v| v.as_str()).map(String::from),
                    from: r_obj.get("from").and_then(|v| v.as_f64()),
                    to: r_obj.get("to").and_then(|v| v.as_f64()),
                });
            }
            Ok(AggType::Range { field, ranges })
        }
        "histogram" => {
            let field = require_field(obj, key)?;
            let interval = obj.get("interval").and_then(|v| v.as_f64())
                .ok_or_else(|| LuciError::InvalidQuery("histogram requires 'interval'".into()))?;
            Ok(AggType::Histogram { field, interval })
        }
        "filter" => {
            let query = parse_query(val)?;
            Ok(AggType::Filter { query })
        }
        "cardinality" => {
            let field = require_field(obj, key)?;
            let precision_threshold = obj.get("precision_threshold")
                .and_then(|v| v.as_u64())
                .unwrap_or(3000) as u32;
            Ok(AggType::Cardinality { field, precision_threshold })
        }
        "percentiles" => {
            let field = require_field(obj, key)?;
            let percents = obj.get("percents")
                .and_then(|v| v.as_array())
                .map(|arr| arr.iter().filter_map(|v| v.as_f64()).collect())
                .unwrap_or_else(|| vec![1.0, 5.0, 25.0, 50.0, 75.0, 95.0, 99.0]);
            let compression = obj.get("tdigest")
                .and_then(|v| v.get("compression"))
                .and_then(|v| v.as_f64())
                .unwrap_or(100.0);
            Ok(AggType::Percentiles { field, percents, compression })
        }
        "geo_bounds" => Ok(AggType::GeoBounds { field: require_field(obj, key)? }),
        "geo_centroid" => Ok(AggType::GeoCentroid { field: require_field(obj, key)? }),
        "nested" => {
            let path = obj.get("path").and_then(|v| v.as_str())
                .ok_or_else(|| LuciError::InvalidQuery("nested agg requires 'path'".into()))?
                .to_string();
            Ok(AggType::Nested { path })
        }
        "reverse_nested" => {
            Ok(AggType::ReverseNested)
        }
        "geohash_grid" => {
            let field = require_field(obj, key)?;
            let precision = obj.get("precision").and_then(|v| v.as_u64()).unwrap_or(5) as usize;
            let size = obj.get("size").and_then(|v| v.as_u64()).unwrap_or(10000) as usize;
            Ok(AggType::GeohashGrid { field, precision, size })
        }
        "top_hits" => {
            let size = obj.get("size").and_then(|v| v.as_u64()).unwrap_or(3) as usize;
            Ok(AggType::TopHits { size })
        }
        "date_histogram" => {
            let field = require_field(obj, key)?;
            let interval = if let Some(cal) = obj.get("calendar_interval").and_then(|v| v.as_str()) {
                let cal_int = match cal {
                    "minute" | "1m" => super::CalendarInterval::Minute,
                    "hour" | "1h" => super::CalendarInterval::Hour,
                    "day" | "1d" => super::CalendarInterval::Day,
                    "week" | "1w" => super::CalendarInterval::Week,
                    "month" | "1M" => super::CalendarInterval::Month,
                    "quarter" | "1q" => super::CalendarInterval::Quarter,
                    "year" | "1y" => super::CalendarInterval::Year,
                    other => return Err(LuciError::InvalidQuery(
                        format!("date_histogram: unknown calendar_interval '{other}'")
                    )),
                };
                super::DateInterval::Calendar(cal_int)
            } else if let Some(fixed) = obj.get("fixed_interval").and_then(|v| v.as_str()) {
                let ms = parse_fixed_interval(fixed)?;
                super::DateInterval::Fixed(ms)
            } else if let Some(interval_str) = obj.get("interval").and_then(|v| v.as_str()) {
                // Legacy "interval" field — try as fixed first
                if let Ok(ms) = parse_fixed_interval(interval_str) {
                    super::DateInterval::Fixed(ms)
                } else {
                    return Err(LuciError::InvalidQuery(
                        format!("date_histogram: invalid interval '{interval_str}'")
                    ));
                }
            } else {
                return Err(LuciError::InvalidQuery(
                    "date_histogram requires 'calendar_interval' or 'fixed_interval'".into()
                ));
            };
            Ok(AggType::DateHistogram { field, interval })
        }
        "date_range" => {
            let field = require_field(obj, key)?;
            let ranges_val = obj.get("ranges").and_then(|v| v.as_array())
                .ok_or_else(|| LuciError::InvalidQuery("date_range requires 'ranges' array".into()))?;
            let mut ranges = Vec::new();
            for r in ranges_val {
                let r_obj = r.as_object().ok_or_else(|| {
                    LuciError::InvalidQuery("date_range: range must be an object".into())
                })?;
                ranges.push(RangeDef {
                    key: r_obj.get("key").and_then(|v| v.as_str()).map(String::from),
                    from: r_obj.get("from").and_then(|v| parse_date_value(v)),
                    to: r_obj.get("to").and_then(|v| parse_date_value(v)),
                });
            }
            Ok(AggType::DateRange { field, ranges })
        }
        "filters" => {
            let filters_obj = obj.get("filters")
                .and_then(|v| v.as_object())
                .ok_or_else(|| LuciError::InvalidQuery("filters requires 'filters' object".into()))?;
            let mut filters = Vec::new();
            for (name, query_val) in filters_obj {
                let query = parse_query(query_val)?;
                filters.push((name.clone(), query));
            }
            Ok(AggType::Filters { filters })
        }
        _ => Err(LuciError::UnsupportedQuery(format!("unknown aggregation type: {key}"))),
    }
}

/// Parse a fixed interval string (e.g., "1d", "12h", "30m", "1000ms") to milliseconds.
fn parse_fixed_interval(s: &str) -> Result<f64> {
    let s = s.trim();
    if let Some(n) = s.strip_suffix("ms") {
        return n.parse::<f64>().map_err(|_| LuciError::InvalidQuery(format!("invalid interval: {s}")));
    }
    if let Some(n) = s.strip_suffix('s') {
        return Ok(n.parse::<f64>().map_err(|_| LuciError::InvalidQuery(format!("invalid interval: {s}")))? * 1_000.0);
    }
    if let Some(n) = s.strip_suffix('m') {
        return Ok(n.parse::<f64>().map_err(|_| LuciError::InvalidQuery(format!("invalid interval: {s}")))? * 60_000.0);
    }
    if let Some(n) = s.strip_suffix('h') {
        return Ok(n.parse::<f64>().map_err(|_| LuciError::InvalidQuery(format!("invalid interval: {s}")))? * 3_600_000.0);
    }
    if let Some(n) = s.strip_suffix('d') {
        return Ok(n.parse::<f64>().map_err(|_| LuciError::InvalidQuery(format!("invalid interval: {s}")))? * 86_400_000.0);
    }
    Err(LuciError::InvalidQuery(format!("invalid fixed_interval: {s}")))
}

/// Parse a date value to epoch millis (f64).
/// Accepts: epoch millis as number, or ISO 8601 string.
fn parse_date_value(v: &Value) -> Option<f64> {
    match v {
        Value::Number(n) => n.as_f64(),
        Value::String(s) => {
            // Try epoch millis as string
            if let Ok(ms) = s.parse::<f64>() {
                return Some(ms);
            }
            // Try ISO 8601 (basic: "2024-01-15T00:00:00Z")
            // Use a simple parser — full chrono is overkill
            if s.len() >= 10 {
                let parts: Vec<&str> = s.split('T').collect();
                let date_parts: Vec<&str> = parts[0].split('-').collect();
                if date_parts.len() == 3 {
                    let y: i64 = date_parts[0].parse().ok()?;
                    let m: i64 = date_parts[1].parse().ok()?;
                    let d: i64 = date_parts[2].parse().ok()?;
                    // Approximate: days since epoch
                    let days = (y - 1970) * 365 + (y - 1969) / 4 + (m - 1) * 30 + d - 1;
                    return Some(days as f64 * 86_400_000.0);
                }
            }
            None
        }
        _ => None,
    }
}

fn require_field(obj: &serde_json::Map<String, Value>, agg_name: &str) -> Result<String> {
    obj.get("field")
        .and_then(|v| v.as_str())
        .map(String::from)
        .ok_or_else(|| LuciError::InvalidQuery(format!("{agg_name} requires 'field'")))
}

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

    #[test]
    fn parse_avg() {
        let aggs = parse_aggs(&json!({"my_avg": {"avg": {"field": "price"}}})).unwrap();
        assert_eq!(aggs.len(), 1);
        assert_eq!(aggs[0].name, "my_avg");
        assert!(matches!(&aggs[0].agg_type, AggType::Avg { field } if field == "price"));
    }

    #[test]
    fn parse_terms_with_size() {
        let aggs = parse_aggs(&json!({"by_tag": {"terms": {"field": "tag", "size": 5}}})).unwrap();
        if let AggType::Terms { field, size } = &aggs[0].agg_type {
            assert_eq!(field, "tag");
            assert_eq!(*size, 5);
        } else { panic!(); }
    }

    #[test]
    fn parse_terms_default_size() {
        let aggs = parse_aggs(&json!({"by_tag": {"terms": {"field": "tag"}}})).unwrap();
        if let AggType::Terms { size, .. } = &aggs[0].agg_type {
            assert_eq!(*size, 10);
        } else { panic!(); }
    }

    #[test]
    fn parse_range() {
        let aggs = parse_aggs(&json!({
            "price_ranges": {"range": {"field": "price", "ranges": [
                {"to": 50.0},
                {"from": 50.0, "to": 100.0},
                {"from": 100.0}
            ]}}
        })).unwrap();
        if let AggType::Range { ranges, .. } = &aggs[0].agg_type {
            assert_eq!(ranges.len(), 3);
        } else { panic!(); }
    }

    #[test]
    fn parse_histogram() {
        let aggs = parse_aggs(&json!({
            "prices": {"histogram": {"field": "price", "interval": 10.0}}
        })).unwrap();
        if let AggType::Histogram { interval, .. } = &aggs[0].agg_type {
            assert_eq!(*interval, 10.0);
        } else { panic!(); }
    }

    #[test]
    fn parse_nested_sub_aggs() {
        let aggs = parse_aggs(&json!({
            "by_tag": {
                "terms": {"field": "tag"},
                "aggs": {
                    "avg_price": {"avg": {"field": "price"}}
                }
            }
        })).unwrap();
        assert_eq!(aggs[0].sub_aggs.len(), 1);
        assert_eq!(aggs[0].sub_aggs[0].name, "avg_price");
    }

    #[test]
    fn parse_multiple_aggs() {
        let aggs = parse_aggs(&json!({
            "total": {"sum": {"field": "amount"}},
            "average": {"avg": {"field": "amount"}}
        })).unwrap();
        assert_eq!(aggs.len(), 2);
    }

    #[test]
    fn parse_filter_agg() {
        let aggs = parse_aggs(&json!({
            "active": {"filter": {"term": {"status": "active"}}}
        })).unwrap();
        assert!(matches!(&aggs[0].agg_type, AggType::Filter { .. }));
    }

    #[test]
    fn unknown_agg_type_error() {
        let r = parse_aggs(&json!({"x": {"unknown_type": {"field": "f"}}}));
        assert!(r.is_err());
    }

    #[test]
    fn missing_field_error() {
        let r = parse_aggs(&json!({"x": {"avg": {}}}));
        assert!(r.is_err());
    }
}
