//! Tests for querying the current values of stats.

use serde::Serialize;

use slog_extlog::{define_stats, xlog};

#[cfg(feature = "derive")]
use slog_extlog::ExtLoggable;
#[cfg(not(feature = "derive"))]
use slog_extlog_derive::ExtLoggable;

use slog_extlog::slog_test::*;
use slog_extlog::stats::StatType::{BucketCounter, Counter, Gauge};
use std::str;

const CRATE_LOG_NAME: &str = "SLOG_STATS_QUERY_TEST";

define_stats! {
    ALL_STATS = {
        test_counter(Counter, "Test counter", []),
        test_gauge(Gauge, "Test gauge", []),
        test_grouped_counter(Counter, "Test grouped counter", ["counter_group_one", "counter_group_two"]),
        test_grouped_gauge(Gauge, "Test grouped gauge", ["gauge_group_one", "gauge_group_two"]),
        test_bucket_counter_freq(BucketCounter, "Test bucket counter", [], (Freq, "bucket", [1, 2, 3])),
        test_bucket_counter_cumul_freq(
            BucketCounter,
            "Test cumulative bucket counter",
            [],
            (CumulFreq, "bucket", [10, 20, 30])
        ),
        test_group_bucket_counter(
            BucketCounter,
            "Test cumulative bucket counter with groups",
            ["group1", "group2"],
            (CumulFreq, "bucket", [-8, 0])
        )
    }
}

//LCOV_EXCL_START
#[derive(ExtLoggable, Clone, Serialize)]
#[LogDetails(Id = "1", Text = "Amount sent", Level = "Info")]
#[StatTrigger(StatName = "test_counter", Action = "Incr", ValueFrom = "self.delta")]
struct CounterUpdateLog {
    delta: i32,
}

#[derive(ExtLoggable, Clone, Serialize)]
#[LogDetails(Id = "2", Text = "Amount sent", Level = "Info")]
#[StatTrigger(StatName = "test_gauge", Action = "Incr", ValueFrom = "self.delta")]
struct GaugeUpdateLog {
    delta: i32,
}

#[derive(ExtLoggable, Clone, Serialize)]
#[LogDetails(Id = "3", Text = "Amount sent", Level = "Info")]
#[StatTrigger(
    StatName = "test_grouped_counter",
    Action = "Incr",
    ValueFrom = "self.delta"
)]
struct GroupedCounterUpdateLog {
    delta: i32,
    #[StatGroup(StatName = "test_grouped_counter")]
    counter_group_one: String,
    #[StatGroup(StatName = "test_grouped_counter")]
    counter_group_two: u32,
}

#[derive(ExtLoggable, Clone, Serialize)]
#[LogDetails(Id = "4", Text = "Amount sent", Level = "Info")]
#[StatTrigger(
    StatName = "test_grouped_gauge",
    Action = "Incr",
    ValueFrom = "self.delta"
)]
struct GroupedGaugeUpdateLog {
    delta: i32,
    #[StatGroup(StatName = "test_grouped_gauge")]
    gauge_group_one: String,
    #[StatGroup(StatName = "test_grouped_gauge")]
    gauge_group_two: u32,
}

#[derive(ExtLoggable, Clone, Serialize)]
#[LogDetails(Id = "5", Text = "test bucket counter stat log", Level = "Info")]
#[StatTrigger(StatName = "test_bucket_counter_freq", Action = "Incr", Value = "1")]
struct BucketCounterLog {
    #[BucketBy(StatName = "test_bucket_counter_freq")]
    bucket_value: f32,
}

#[derive(ExtLoggable, Clone, Serialize)]
#[LogDetails(
    Id = "5",
    Text = "cumulative test bucket counter stat log",
    Level = "Info"
)]
#[StatTrigger(
    StatName = "test_bucket_counter_cumul_freq",
    Action = "Incr",
    Value = "2"
)]
struct CumulBucketCounterLog {
    #[BucketBy(StatName = "test_bucket_counter_cumul_freq")]
    bucket_value: f32,
}

#[derive(ExtLoggable, Clone, Serialize)]
#[LogDetails(
    Id = "6",
    Text = "test grouped bucket counter stat log",
    Level = "Info"
)]
#[StatTrigger(
    StatName = "test_group_bucket_counter",
    Action = "Incr",
    ValueFrom = "self.delta"
)]
struct GroupBucketCounterLog {
    delta: i32,
    #[StatGroup(StatName = "test_group_bucket_counter")]
    group1: String,
    #[StatGroup(StatName = "test_group_bucket_counter")]
    group2: String,
    #[BucketBy(StatName = "test_group_bucket_counter")]
    bucket_value: f32,
}
//LCOV_EXCL_STOP

#[tokio::test]
async fn request_with_no_stats() {
    let (logger, _) = create_logger_buffer(&[]);
    let stats = logger.get_stats();

    assert_eq!(stats.len(), 0);
}

#[tokio::test]
async fn request_for_single_counter() {
    static STATS: StatDefinitions = &[&test_counter];
    let (logger, _) = create_logger_buffer(STATS);
    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_counter",
            description: "Test counter",
            stat_type: Counter,
            values: vec![ExpectedStatSnapshotValue {
                group_values: vec![],
                bucket_limit: None,
                value: 0f64,
            }],
            buckets: None,
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_single_gauge() {
    static STATS: StatDefinitions = &[&test_gauge];
    let (logger, _) = create_logger_buffer(STATS);
    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_gauge",
            description: "Test gauge",
            stat_type: Gauge,
            values: vec![ExpectedStatSnapshotValue {
                group_values: vec![],
                bucket_limit: None,
                value: 0f64,
            }],
            buckets: None,
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_multiple_metrics() {
    static STATS: StatDefinitions = &[&test_counter, &test_gauge];
    let (logger, _) = create_logger_buffer(STATS);
    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[
            ExpectedStatSnapshot {
                name: "test_counter",
                description: "Test counter",
                stat_type: Counter,
                values: vec![ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: None,
                    value: 0f64,
                }],
                buckets: None,
            },
            ExpectedStatSnapshot {
                name: "test_gauge",
                description: "Test gauge",
                stat_type: Gauge,
                values: vec![ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: None,
                    value: 0f64,
                }],
                buckets: None,
            },
        ], // LCOV_EXCL_LINE Kcov bug?
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_updated_metrics() {
    static STATS: StatDefinitions = &[&test_counter, &test_gauge];
    let (logger, _) = create_logger_buffer(STATS);

    xlog!(logger, CounterUpdateLog { delta: 1 });
    xlog!(logger, GaugeUpdateLog { delta: 2 });

    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[
            ExpectedStatSnapshot {
                name: "test_counter",
                description: "Test counter",
                stat_type: Counter,
                values: vec![ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: None,
                    value: 1f64,
                }],
                buckets: None,
            },
            ExpectedStatSnapshot {
                name: "test_gauge",
                description: "Test gauge",
                stat_type: Gauge,
                values: vec![ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: None,
                    value: 2f64,
                }],
                buckets: None,
            },
        ], // LCOV_EXCL_LINE Kcov bug?
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_single_counter_with_groups_but_no_values() {
    static STATS: StatDefinitions = &[&test_grouped_counter];
    let (logger, _) = create_logger_buffer(STATS);
    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_grouped_counter",
            description: "Test grouped counter",
            stat_type: Counter,
            values: vec![],
            buckets: None,
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_single_gauge_with_groups_but_no_values() {
    static STATS: StatDefinitions = &[&test_grouped_gauge];
    let (logger, _) = create_logger_buffer(STATS);
    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_grouped_gauge",
            description: "Test grouped gauge",
            stat_type: Gauge,
            values: vec![],
            buckets: None,
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_single_counter_with_groups_and_one_value() {
    static STATS: StatDefinitions = &[&test_grouped_counter];
    let (logger, _) = create_logger_buffer(STATS);

    xlog!(
        logger,
        GroupedCounterUpdateLog {
            delta: 1,
            counter_group_one: "value one".to_string(),
            counter_group_two: 100,
        }
    );

    let stats = logger.get_stats();
    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_grouped_counter",
            description: "Test grouped counter",
            stat_type: Counter,
            values: vec![ExpectedStatSnapshotValue {
                group_values: vec!["value one".to_string(), "100".to_string()],
                bucket_limit: None,
                value: 1f64,
            }],
            buckets: None,
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_single_gauge_with_groups_and_one_value() {
    static STATS: StatDefinitions = &[&test_grouped_gauge];
    let (logger, _) = create_logger_buffer(STATS);

    xlog!(
        logger,
        GroupedGaugeUpdateLog {
            delta: 2,
            gauge_group_one: "value two".to_string(),
            gauge_group_two: 200,
        }
    );

    let stats = logger.get_stats();
    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_grouped_gauge",
            description: "Test grouped gauge",
            stat_type: Gauge,
            values: vec![ExpectedStatSnapshotValue {
                group_values: vec!["value two".to_string(), "200".to_string()],
                bucket_limit: None,
                value: 2f64,
            }],
            buckets: None,
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_single_counter_with_groups_and_two_values() {
    static STATS: StatDefinitions = &[&test_grouped_counter];
    let (logger, _) = create_logger_buffer(STATS);

    xlog!(
        logger,
        GroupedCounterUpdateLog {
            delta: 1,
            counter_group_one: "value one".to_string(),
            counter_group_two: 100,
        }
    );

    xlog!(
        logger,
        GroupedCounterUpdateLog {
            delta: 2,
            counter_group_one: "value two".to_string(),
            counter_group_two: 200,
        }
    );

    let stats = logger.get_stats();
    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_grouped_counter",
            description: "Test grouped counter",
            stat_type: Counter,
            values: vec![
                ExpectedStatSnapshotValue {
                    group_values: vec!["value one".to_string(), "100".to_string()],
                    bucket_limit: None,
                    value: 1f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec!["value two".to_string(), "200".to_string()],
                    bucket_limit: None,
                    value: 2f64,
                },
            ], // LCOV_EXCL_LINE Kcov bug?
            buckets: None,
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_bucket_counter_freq() {
    static STATS: StatDefinitions = &[&test_bucket_counter_freq];
    let (logger, _) = create_logger_buffer(STATS);
    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_bucket_counter_freq",
            description: "Test bucket counter",
            stat_type: BucketCounter,
            values: vec![
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(1)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(2)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(3)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Unbounded),
                    value: 0f64,
                },
            ],
            buckets: Some(Buckets::new(BucketMethod::Freq, "bucket", &[1, 2, 3])),
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_bucket_counter_freq_one_value() {
    static STATS: StatDefinitions = &[&test_bucket_counter_freq];
    let (logger, _) = create_logger_buffer(STATS);

    xlog!(logger, BucketCounterLog { bucket_value: 1.5 });

    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_bucket_counter_freq",
            description: "Test bucket counter",
            stat_type: BucketCounter,
            values: vec![
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(1)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(2)),
                    value: 1f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(3)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Unbounded),
                    value: 0f64,
                },
            ],
            buckets: Some(Buckets::new(BucketMethod::Freq, "bucket", &[1, 2, 3])),
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_bucket_counter_cumul_freq() {
    static STATS: StatDefinitions = &[&test_bucket_counter_cumul_freq];
    let (logger, _) = create_logger_buffer(STATS);
    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_bucket_counter_cumul_freq",
            description: "Test cumulative bucket counter",
            stat_type: BucketCounter,
            values: vec![
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(10)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(20)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Num(30)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: Some(BucketLimit::Unbounded),
                    value: 0f64,
                },
            ],
            buckets: Some(Buckets::new(
                BucketMethod::CumulFreq,
                "bucket",
                &[10, 20, 30],
            )),
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_bucket_counter_with_groups_and_two_values() {
    static STATS: StatDefinitions = &[&test_group_bucket_counter];
    let (logger, _) = create_logger_buffer(STATS);

    xlog!(
        logger,
        GroupBucketCounterLog {
            delta: 3,
            group1: "one".to_string(),
            group2: "two".to_string(),
            bucket_value: 7.4
        } // LCOV_EXCL_LINE Kcov bug?
    );
    xlog!(
        logger,
        GroupBucketCounterLog {
            delta: 4,
            group1: "three".to_string(),
            group2: "four".to_string(),
            bucket_value: -20f32
        } // LCOV_EXCL_LINE Kcov bug?
    );

    let stats = logger.get_stats();

    check_expected_stat_snapshots(
        &stats,
        &[ExpectedStatSnapshot {
            name: "test_group_bucket_counter",
            description: "Test cumulative bucket counter with groups",
            stat_type: BucketCounter,
            values: vec![
                ExpectedStatSnapshotValue {
                    group_values: vec!["one".to_string(), "two".to_string()],
                    bucket_limit: Some(BucketLimit::Num(-8)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec!["one".to_string(), "two".to_string()],
                    bucket_limit: Some(BucketLimit::Num(0)),
                    value: 0f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec!["one".to_string(), "two".to_string()],
                    bucket_limit: Some(BucketLimit::Unbounded),
                    value: 3f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec!["three".to_string(), "four".to_string()],
                    bucket_limit: Some(BucketLimit::Num(-8)),
                    value: 4f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec!["three".to_string(), "four".to_string()],
                    bucket_limit: Some(BucketLimit::Num(0)),
                    value: 4f64,
                },
                ExpectedStatSnapshotValue {
                    group_values: vec!["three".to_string(), "four".to_string()],
                    bucket_limit: Some(BucketLimit::Unbounded),
                    value: 4f64,
                },
            ],
            buckets: Some(Buckets::new(BucketMethod::CumulFreq, "bucket", &[-8, 0])),
        }],
    ); // LCOV_EXCL_LINE Kcov bug?
}

#[tokio::test]
async fn request_for_many_metrics() {
    let (logger, _) = create_logger_buffer(ALL_STATS);

    xlog!(logger, CounterUpdateLog { delta: 1 });
    xlog!(logger, GaugeUpdateLog { delta: 2 });
    xlog!(
        logger,
        GroupedCounterUpdateLog {
            delta: 3,
            counter_group_one: "value one".to_string(),
            counter_group_two: 100,
        }
    );

    xlog!(
        logger,
        GroupedCounterUpdateLog {
            delta: 4,
            counter_group_one: "value two".to_string(),
            counter_group_two: 200,
        }
    );
    xlog!(
        logger,
        GroupedGaugeUpdateLog {
            delta: 5,
            gauge_group_one: "value three".to_string(),
            gauge_group_two: 300,
        }
    );

    xlog!(
        logger,
        GroupedGaugeUpdateLog {
            delta: 6,
            gauge_group_one: "value four".to_string(),
            gauge_group_two: 400,
        }
    );

    let stats = logger.get_stats();
    check_expected_stat_snapshots(
        &stats,
        &[
            ExpectedStatSnapshot {
                name: "test_counter",
                description: "Test counter",
                stat_type: Counter,
                values: vec![ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: None,
                    value: 1f64,
                }],
                buckets: None,
            },
            ExpectedStatSnapshot {
                name: "test_gauge",
                description: "Test gauge",
                stat_type: Gauge,
                values: vec![ExpectedStatSnapshotValue {
                    group_values: vec![],
                    bucket_limit: None,
                    value: 2f64,
                }],
                buckets: None,
            },
            ExpectedStatSnapshot {
                name: "test_grouped_counter",
                description: "Test grouped counter",
                stat_type: Counter,
                values: vec![
                    ExpectedStatSnapshotValue {
                        group_values: vec!["value one".to_string(), "100".to_string()],
                        bucket_limit: None,
                        value: 3f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec!["value two".to_string(), "200".to_string()],
                        bucket_limit: None,
                        value: 4f64,
                    },
                ], // LCOV_EXCL_LINE Kcov bug?
                buckets: None,
            },
            ExpectedStatSnapshot {
                name: "test_grouped_gauge",
                description: "Test grouped gauge",
                stat_type: Gauge,
                values: vec![
                    ExpectedStatSnapshotValue {
                        group_values: vec!["value three".to_string(), "300".to_string()],
                        bucket_limit: None,
                        value: 5f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec!["value four".to_string(), "400".to_string()],
                        bucket_limit: None,
                        value: 6f64,
                    },
                ], // LCOV_EXCL_LINE Kcov bug?
                buckets: None,
            },
            ExpectedStatSnapshot {
                name: "test_bucket_counter_freq",
                description: "Test bucket counter",
                stat_type: BucketCounter,
                values: vec![
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(1)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(2)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(3)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Unbounded),
                        value: 0f64,
                    },
                ],
                buckets: Some(Buckets::new(BucketMethod::Freq, "bucket", &[1, 2, 3])),
            },
            ExpectedStatSnapshot {
                name: "test_bucket_counter_cumul_freq",
                description: "Test cumulative bucket counter",
                stat_type: BucketCounter,
                values: vec![
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(10)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(20)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(30)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Unbounded),
                        value: 0f64,
                    },
                ],
                buckets: Some(Buckets::new(
                    BucketMethod::CumulFreq,
                    "bucket",
                    &[10, 20, 30],
                )),
            },
            ExpectedStatSnapshot {
                name: "test_bucket_counter_cumul_freq",
                description: "Test cumulative bucket counter",
                stat_type: BucketCounter,
                values: vec![
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(10)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(20)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Num(30)),
                        value: 0f64,
                    },
                    ExpectedStatSnapshotValue {
                        group_values: vec![],
                        bucket_limit: Some(BucketLimit::Unbounded),
                        value: 0f64,
                    },
                ],
                buckets: Some(Buckets::new(
                    BucketMethod::CumulFreq,
                    "bucket",
                    &[10, 20, 30],
                )),
            },
        ], // LCOV_EXCL_LINE Kcov bug?
    ); // LCOV_EXCL_LINE Kcov bug?
}
