use serde::Serialize;
use serde::ser::SerializeMap;

use crate::plugins::telemetry::metrics::apollo::histogram::Histogram;
use crate::plugins::telemetry::metrics::apollo::histogram::HistogramConfig;
use crate::plugins::telemetry::metrics::apollo::histogram::MAXIMUM_SIZE;

pub(crate) type ListLengthHistogram = Histogram<ListLengthConfig>;
#[derive(Debug, Clone, Serialize)]
pub(crate) struct ListLengthConfig;
impl HistogramConfig for ListLengthConfig {
    type Value = u64;
    type HistogramType = u64;

    fn bucket(value: Self::Value) -> usize {
        (if value < 100 {
            value
        } else if value < 1000 {
            90 + value / 10
        } else if value < 10000 {
            180 + value / 100
        } else if value < 114000 {
            270 + value / 1000
        } else {
            (MAXIMUM_SIZE - 1) as u64
        }) as usize
    }

    fn convert(value: Self::Value) -> Self::HistogramType {
        value
    }
}

impl Serialize for Histogram<ListLengthConfig> {
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut map = serializer.serialize_map(None)?;
        map.serialize_entry("buckets", &self.clone().buckets_to_f64())?;
        map.serialize_entry("total", &self.total_f64())?;
        map.serialize_entry("max", &self.max_f64())?;
        map.end()
    }
}

#[cfg(test)]
mod test {
    use crate::plugins::telemetry::metrics::apollo::histogram::ListLengthHistogram;
    use crate::plugins::telemetry::metrics::apollo::histogram::MAXIMUM_SIZE;

    #[test]
    fn list_length_bucketing() {
        let mut hist = ListLengthHistogram::new(None);

        for i in 0..120000 {
            hist.record(Some(i), 1);
        }

        let v = hist.buckets_to_i64();
        assert_eq!(v.len(), MAXIMUM_SIZE);

        for (i, item) in v.iter().enumerate().take(100) {
            assert_eq!(*item, 1, "testing contents of bucket {i}");
        }
        for (i, item) in v.iter().enumerate().take(190).skip(100) {
            assert_eq!(*item, 10, "testing contents of bucket {i}");
        }
        for (i, item) in v.iter().enumerate().take(280).skip(190) {
            assert_eq!(*item, 100, "testing contents of bucket {i}");
        }
        for (i, item) in v.iter().enumerate().take(382).skip(280) {
            assert_eq!(*item, 1000, "testing contents of bucket {i}");
        }
        assert_eq!(v[MAXIMUM_SIZE - 1], 7000, "testing contents of last bucket");
    }

    #[test]
    fn it_generates_populated_histogram() {
        let mut histogram = ListLengthHistogram::new(None);
        histogram.record(Some(1), 1);
        assert_eq!(histogram.to_array(), vec![0, 1]);
        histogram.record(Some(2), 1);
        assert_eq!(histogram.to_array(), vec![0, 1, 1]);
        histogram.record(Some(2), 3);
        assert_eq!(histogram.to_array(), vec![0, 1, 4]);
    }
}
