use crate::event::{
    metric::{Bucket, MetricData, MetricName, MetricSeries, Quantile, Sample},
    Event, EventMetadata, LogEvent, Metric, MetricKind, MetricValue, StatisticKind, Value,
};
use bytes::Bytes;
use chrono::{DateTime, NaiveDateTime, Utc};
use quickcheck::{empty_shrinker, Arbitrary, Gen};
use std::collections::{BTreeMap, BTreeSet};

const MAX_F64_SIZE: f64 = 1_000_000.0;
const MAX_MAP_SIZE: usize = 4;
const MAX_STR_SIZE: usize = 16;
const ALPHABET: [&str; 27] = [
    "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
    "t", "u", "v", "w", "x", "y", "z", "_",
];

#[derive(Debug, Clone)]
struct Name {
    inner: String,
}

impl Arbitrary for Name {
    fn arbitrary(g: &mut Gen) -> Self {
        let mut name = String::with_capacity(MAX_STR_SIZE);
        for _ in 0..(g.size() % MAX_STR_SIZE) {
            let idx: usize = usize::arbitrary(g) % ALPHABET.len();
            name.push_str(ALPHABET[idx]);
        }

        Name { inner: name }
    }
}

impl From<Name> for String {
    fn from(name: Name) -> String {
        name.inner
    }
}

fn datetime(g: &mut Gen) -> DateTime<Utc> {
    // chrono documents that there is an out-of-range for both second and
    // nanosecond values but doesn't actually document what the valid ranges
    // are. We just sort of arbitrarily restrict things.
    let secs = i64::arbitrary(g) % 32_000;
    let nanosecs = u32::arbitrary(g) % 32_000;
    DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(secs, nanosecs), Utc)
}

impl Arbitrary for Event {
    fn arbitrary(g: &mut Gen) -> Self {
        let choice: u8 = u8::arbitrary(g);
        // Quickcheck can't derive Arbitrary for enums, see
        // https://github.com/BurntSushi/quickcheck/issues/98
        if choice % 2 == 0 {
            Event::Log(LogEvent::arbitrary(g))
        } else {
            Event::Metric(Metric::arbitrary(g))
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        match self {
            Event::Log(log_event) => Box::new(log_event.shrink().map(Event::Log)),
            Event::Metric(metric) => Box::new(metric.shrink().map(Event::Metric)),
        }
    }
}

impl Arbitrary for LogEvent {
    fn arbitrary(g: &mut Gen) -> Self {
        let mut gen = Gen::new(MAX_MAP_SIZE);
        let map: BTreeMap<String, Value> = BTreeMap::arbitrary(&mut gen);
        let metadata: EventMetadata = EventMetadata::arbitrary(g);
        LogEvent::from_parts(map, metadata)
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let (fields, metadata) = self.clone().into_parts();

        Box::new(
            fields
                .shrink()
                .map(move |x| LogEvent::from_parts(x, metadata.clone())),
        )
    }
}

impl Arbitrary for Metric {
    fn arbitrary(g: &mut Gen) -> Self {
        let name = String::from(Name::arbitrary(g));
        let kind = MetricKind::arbitrary(g);
        let value = MetricValue::arbitrary(g);
        let metadata = EventMetadata::arbitrary(g);
        let mut metric = Metric::new_with_metadata(name, kind, value, metadata);
        metric.data = MetricData::arbitrary(g);
        metric.series = MetricSeries::arbitrary(g);

        metric
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let metric = self.clone();
        let name = String::from(metric.name());

        Box::new(
            name.shrink()
                .map(move |name| metric.clone().with_name(name))
                .flat_map(|metric| {
                    let data = metric.data.clone();
                    data.shrink().map(move |data| {
                        let mut new_metric = metric.clone();
                        new_metric.data = data;
                        new_metric
                    })
                })
                .flat_map(|metric| {
                    let series = metric.series.clone();
                    series.shrink().map(move |series| {
                        let mut new_metric = metric.clone();
                        new_metric.series = series;
                        new_metric
                    })
                }),
        )
    }
}

impl Arbitrary for MetricKind {
    fn arbitrary(g: &mut Gen) -> Self {
        let choice: u8 = u8::arbitrary(g);
        // Quickcheck can't derive Arbitrary for enums, see
        // https://github.com/BurntSushi/quickcheck/issues/98
        if choice % 2 == 0 {
            MetricKind::Incremental
        } else {
            MetricKind::Absolute
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        empty_shrinker()
    }
}

impl Arbitrary for MetricValue {
    fn arbitrary(g: &mut Gen) -> Self {
        // Quickcheck can't derive Arbitrary for enums, see
        // https://github.com/BurntSushi/quickcheck/issues/98.  The magical
        // constant here are the number of fields in `MetricValue`. Because the
        // field total is not a power of two we introduce a bias into choice
        // here toward `MetricValue::Counter` and `MetricValue::Gauge`.
        match u8::arbitrary(g) % 6 {
            0 => MetricValue::Counter {
                value: f64::arbitrary(g) % MAX_F64_SIZE,
            },
            1 => MetricValue::Gauge {
                value: f64::arbitrary(g) % MAX_F64_SIZE,
            },
            2 => MetricValue::Set {
                values: BTreeSet::arbitrary(g),
            },
            3 => MetricValue::Distribution {
                samples: Vec::arbitrary(g),
                statistic: StatisticKind::arbitrary(g),
            },
            4 => MetricValue::AggregatedHistogram {
                buckets: Vec::arbitrary(g),
                count: u32::arbitrary(g),
                sum: f64::arbitrary(g) % MAX_F64_SIZE,
            },
            5 => MetricValue::AggregatedSummary {
                quantiles: Vec::arbitrary(g),
                count: u32::arbitrary(g),
                sum: f64::arbitrary(g) % MAX_F64_SIZE,
            },
            _ => unreachable!(),
        }
    }

    #[allow(clippy::too_many_lines)] // no real way to make this shorter
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        match self {
            MetricValue::Counter { value } => {
                Box::new(value.shrink().map(|value| MetricValue::Counter { value }))
            }
            MetricValue::Gauge { value } => {
                Box::new(value.shrink().map(|value| MetricValue::Gauge { value }))
            }
            MetricValue::Set { values } => {
                Box::new(values.shrink().map(|values| MetricValue::Set { values }))
            }
            MetricValue::Distribution { samples, statistic } => {
                let statistic = *statistic;
                Box::new(
                    samples
                        .shrink()
                        .map(move |samples| MetricValue::Distribution { samples, statistic })
                        .flat_map(|metric_value| match metric_value {
                            MetricValue::Distribution { samples, statistic } => statistic
                                .shrink()
                                .map(move |statistic| MetricValue::Distribution {
                                    samples: samples.clone(),
                                    statistic,
                                }),
                            _ => unreachable!(),
                        }),
                )
            }
            MetricValue::AggregatedHistogram {
                buckets,
                count,
                sum,
            } => {
                let buckets = buckets.clone();
                let count = *count;
                let sum = *sum;

                Box::new(
                    buckets
                        .shrink()
                        .map(move |buckets| MetricValue::AggregatedHistogram {
                            buckets,
                            count,
                            sum,
                        })
                        .flat_map(move |hist| match hist {
                            MetricValue::AggregatedHistogram {
                                buckets,
                                count,
                                sum,
                            } => {
                                count
                                    .shrink()
                                    .map(move |count| MetricValue::AggregatedHistogram {
                                        buckets: buckets.clone(),
                                        count,
                                        sum,
                                    })
                            }
                            _ => unreachable!(),
                        })
                        .flat_map(move |hist| match hist {
                            MetricValue::AggregatedHistogram {
                                buckets,
                                count,
                                sum,
                            } => sum
                                .shrink()
                                .map(move |sum| MetricValue::AggregatedHistogram {
                                    buckets: buckets.clone(),
                                    count,
                                    sum,
                                }),
                            _ => unreachable!(),
                        }),
                )
            }
            MetricValue::AggregatedSummary {
                quantiles,
                count,
                sum,
            } => {
                let quantiles = quantiles.clone();
                let count = *count;
                let sum = *sum;

                Box::new(
                    quantiles
                        .shrink()
                        .map(move |quantiles| MetricValue::AggregatedSummary {
                            quantiles,
                            count,
                            sum,
                        })
                        .flat_map(move |hist| match hist {
                            MetricValue::AggregatedSummary {
                                quantiles,
                                count,
                                sum,
                            } => count
                                .shrink()
                                .map(move |count| MetricValue::AggregatedSummary {
                                    quantiles: quantiles.clone(),
                                    count,
                                    sum,
                                }),
                            _ => unreachable!(),
                        })
                        .flat_map(move |hist| match hist {
                            MetricValue::AggregatedSummary {
                                quantiles,
                                count,
                                sum,
                            } => sum.shrink().map(move |sum| MetricValue::AggregatedSummary {
                                quantiles: quantiles.clone(),
                                count,
                                sum,
                            }),
                            _ => unreachable!(),
                        }),
                )
            }
        }
    }
}

impl Arbitrary for Sample {
    fn arbitrary(g: &mut Gen) -> Self {
        Sample {
            value: f64::arbitrary(g) % MAX_F64_SIZE,
            rate: u32::arbitrary(g),
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let base = *self;

        Box::new(
            base.value
                .shrink()
                .map(move |value| {
                    let mut sample = base;
                    sample.value = value;
                    sample
                })
                .flat_map(|sample| {
                    sample.rate.shrink().map(move |rate| {
                        let mut ns = sample;
                        ns.rate = rate;
                        ns
                    })
                }),
        )
    }
}

impl Arbitrary for Quantile {
    fn arbitrary(g: &mut Gen) -> Self {
        Quantile {
            upper_limit: f64::arbitrary(g) % MAX_F64_SIZE,
            value: f64::arbitrary(g) % MAX_F64_SIZE,
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let base = *self;

        Box::new(
            base.upper_limit
                .shrink()
                .map(move |upper_limit| {
                    let mut quantile = base;
                    quantile.upper_limit = upper_limit;
                    quantile
                })
                .flat_map(|quantile| {
                    quantile.value.shrink().map(move |value| {
                        let mut nq = quantile;
                        nq.value = value;
                        nq
                    })
                }),
        )
    }
}

impl Arbitrary for Bucket {
    fn arbitrary(g: &mut Gen) -> Self {
        Bucket {
            upper_limit: f64::arbitrary(g) % MAX_F64_SIZE,
            count: u32::arbitrary(g),
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let base = *self;

        Box::new(
            base.upper_limit
                .shrink()
                .map(move |upper_limit| {
                    let mut nb = base;
                    nb.upper_limit = upper_limit;
                    nb
                })
                .flat_map(|bucket| {
                    bucket.count.shrink().map(move |count| {
                        let mut nb = bucket;
                        nb.count = count;
                        nb
                    })
                }),
        )
    }
}

impl Arbitrary for StatisticKind {
    fn arbitrary(g: &mut Gen) -> Self {
        let choice: u8 = u8::arbitrary(g);
        // Quickcheck can't derive Arbitrary for enums, see
        // https://github.com/BurntSushi/quickcheck/issues/98
        if choice % 2 == 0 {
            StatisticKind::Histogram
        } else {
            StatisticKind::Summary
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        empty_shrinker()
    }
}

impl Arbitrary for MetricSeries {
    fn arbitrary(g: &mut Gen) -> Self {
        let tags = if bool::arbitrary(g) {
            let mut map: BTreeMap<String, String> = BTreeMap::new();
            for _ in 0..(usize::arbitrary(g) % MAX_MAP_SIZE) {
                let key = String::from(Name::arbitrary(g));
                let value = String::from(Name::arbitrary(g));
                map.insert(key, value);
            }
            if map.is_empty() {
                None
            } else {
                Some(map)
            }
        } else {
            None
        };

        MetricSeries {
            name: MetricName::arbitrary(g),
            tags,
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let metric_series = self.clone();

        Box::new(
            metric_series
                .name
                .shrink()
                .map(move |nme| {
                    let mut ms = metric_series.clone();
                    ms.name = nme;
                    ms
                })
                .flat_map(|metric_series| {
                    metric_series.tags.shrink().map(move |tgs| {
                        let mut ms = metric_series.clone();
                        ms.tags = tgs;
                        ms
                    })
                }),
        )
    }
}

impl Arbitrary for MetricName {
    fn arbitrary(g: &mut Gen) -> Self {
        let namespace = if bool::arbitrary(g) {
            Some(String::from(Name::arbitrary(g)))
        } else {
            None
        };

        MetricName {
            name: String::from(Name::arbitrary(g)),
            namespace,
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let metric_name = self.clone();

        Box::new(
            metric_name
                .name
                .shrink()
                .map(move |name| {
                    let mut mn = metric_name.clone();
                    mn.name = name;
                    mn
                })
                .flat_map(|metric_name| {
                    metric_name.namespace.shrink().map(move |namespace| {
                        let mut mn = metric_name.clone();
                        mn.namespace = namespace;
                        mn
                    })
                }),
        )
    }
}

impl Arbitrary for MetricData {
    fn arbitrary(g: &mut Gen) -> Self {
        let dt = if bool::arbitrary(g) {
            Some(datetime(g))
        } else {
            None
        };

        MetricData {
            timestamp: dt,
            kind: MetricKind::arbitrary(g),
            value: MetricValue::arbitrary(g),
        }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        let metric_data = self.clone();

        Box::new(
            metric_data
                .kind
                .shrink()
                .map(move |kind| {
                    let mut md = metric_data.clone();
                    md.kind = kind;
                    md
                })
                .flat_map(|metric_data| {
                    metric_data.value.shrink().map(move |value| {
                        let mut md = metric_data.clone();
                        md.value = value;
                        md
                    })
                }),
        )
    }
}

impl Arbitrary for Value {
    fn arbitrary(g: &mut Gen) -> Self {
        // Quickcheck can't derive Arbitrary for enums, see
        // https://github.com/BurntSushi/quickcheck/issues/98.  The magical
        // constant here are the number of fields in `Value`. Because the field
        // total is a power of two we, happily, don't introduce a bias into the
        // field picking.
        match u8::arbitrary(g) % 8 {
            0 => {
                let bytes: Vec<u8> = Vec::arbitrary(g);
                Value::Bytes(Bytes::from(bytes))
            }
            1 => Value::Integer(i64::arbitrary(g)),
            2 => Value::Float(f64::arbitrary(g) % MAX_F64_SIZE),
            3 => Value::Boolean(bool::arbitrary(g)),
            4 => Value::Timestamp(datetime(g)),
            5 => {
                let mut gen = Gen::new(MAX_MAP_SIZE);
                Value::Map(BTreeMap::arbitrary(&mut gen))
            }
            6 => Value::Array(Vec::arbitrary(g)),
            7 => Value::Null,
            _ => unreachable!(),
        }
    }
}

impl Arbitrary for EventMetadata {
    fn arbitrary(_g: &mut Gen) -> Self {
        EventMetadata::default()
    }
}
