// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

mod common;
use crate::common::*;

use glean_core::metrics::*;
use glean_core::CommonMetricData;
use glean_core::Glean;
use glean_core::Lifetime;

fn nofollows_ping(glean: &mut Glean) -> PingType {
    // When `follows_collection_enabled=false` then by default `enabled=false`
    let ping = PingBuilder::new("nofollows")
        .with_send_if_empty(true)
        .with_include_info_sections(false)
        .with_enabled(false)
        .with_follows_collection_enabled(false)
        .build();
    glean.register_ping_type(&ping);
    ping
}

fn manual_ping(glean: &mut Glean) -> PingType {
    let ping = PingBuilder::new("manual").build();
    glean.register_ping_type(&ping);
    ping
}

#[test]
fn pings_with_follows_false_are_exempt() {
    let (mut glean, _t) = new_glean(None);

    let ping = nofollows_ping(&mut glean);

    // We need to store a metric as an empty ping is not stored.
    let counter = CounterMetric::new(CommonMetricData {
        name: "counter".into(),
        category: "local".into(),
        send_in_pings: vec!["nofollows".into()],
        ..Default::default()
    });

    counter.add_sync(&glean, 1);
    assert!(!ping.submit_sync(&glean, None));

    glean.set_ping_enabled(&ping, true);
    counter.add_sync(&glean, 2);
    assert!(ping.submit_sync(&glean, None));

    let mut queued_pings = get_queued_pings(glean.get_data_path()).unwrap();
    assert_eq!(1, queued_pings.len());

    let json = queued_pings.pop().unwrap().1;
    let counter_val = json["metrics"]["counter"]["local.counter"]
        .as_i64()
        .unwrap();
    assert_eq!(2, counter_val);

    glean.set_upload_enabled(false);
    assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
    // Disabling upload generates a deletion ping
    assert_eq!(1, get_deletion_pings(glean.get_data_path()).unwrap().len());

    // Regardless, the `nofollows` ping is still enabled.
    counter.add_sync(&glean, 10);
    assert!(ping.submit_sync(&glean, None));

    let queued_pings = get_queued_pings(glean.get_data_path()).unwrap();
    // both `nofollows` pings remain in the queue
    assert_eq!(2, queued_pings.len());

    let mut values = vec![2, 10];
    for ping in queued_pings {
        let json = ping.1;
        let counter_val = json["metrics"]["counter"]["local.counter"]
            .as_i64()
            .unwrap();

        assert!(values.contains(&counter_val));
        values.retain(|&x| x != counter_val);
    }
}

#[test]
fn nofollows_ping_can_ride_along() {
    let (mut glean, _t) = new_glean(None);

    let nofollows_ping = nofollows_ping(&mut glean);
    // Basically `manual_ping` but with a ride-along
    let manual_ping = PingBuilder::new("manual")
        .with_schedules_pings(vec!["nofollows".to_string()])
        .build();
    glean.register_ping_type(&manual_ping);

    // We need to store a metric as an empty ping is not stored.
    let counter = CounterMetric::new(CommonMetricData {
        name: "counter".into(),
        category: "local".into(),
        send_in_pings: vec!["manual".into(), "nofollows".into()],
        lifetime: Lifetime::Application,
        ..Default::default()
    });

    // Trigger a ping with data.
    counter.add_sync(&glean, 1);
    assert!(manual_ping.submit_sync(&glean, None));
    assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());

    // Enable one more ping, trigger it with more data
    glean.set_ping_enabled(&nofollows_ping, true);
    counter.add_sync(&glean, 2);
    assert!(manual_ping.submit_sync(&glean, None));
    // The previous one, plus 2 new ones
    let queued_pings = get_queued_pings(glean.get_data_path()).unwrap();
    assert_eq!(3, queued_pings.len());
    for (_url, json, info) in queued_pings {
        let Some(obj) = info else {
            panic!("no ping info")
        };
        let counter_val = json["metrics"]["counter"]["local.counter"]
            .as_i64()
            .unwrap();

        if obj["ping_name"].as_str().unwrap() == "nofollows" {
            assert_eq!(2, counter_val, "{json:?}");
        } else {
            let seq = json["ping_info"]["seq"].as_i64().unwrap();

            match seq {
                0 => assert_eq!(1, counter_val),
                1 => assert_eq!(3, counter_val),
                2 => assert_eq!(8, counter_val),
                _ => panic!("unexpected sequence number: {seq}"),
            }
        }
    }

    // Disable it again
    glean.set_ping_enabled(&nofollows_ping, false);
    counter.add_sync(&glean, 5);
    assert!(manual_ping.submit_sync(&glean, None));
    let queued_pings = get_queued_pings(glean.get_data_path()).unwrap();
    // The 2 previous `manual` pings, the `nofollows` was removed, plus the new `manual` one
    assert_eq!(3, queued_pings.len());

    // Check that all follows are as expected.
    // We cannot guarantee order, so we need to look at some values
    for (_url, json, info) in queued_pings {
        let Some(obj) = info else {
            panic!("no ping info")
        };
        let counter_val = json["metrics"]["counter"]["local.counter"]
            .as_i64()
            .unwrap();

        assert_ne!("nofollows", obj["ping_name"].as_str().unwrap());
        let seq = json["ping_info"]["seq"].as_i64().unwrap();

        match seq {
            0 => assert_eq!(1, counter_val),
            1 => assert_eq!(3, counter_val),
            2 => assert_eq!(8, counter_val),
            _ => panic!("unexpected sequence number: {seq}"),
        }
    }
}

#[test]
fn queued_nofollows_pings_are_not_removed() {
    let (mut glean, t) = new_glean(None);

    let nofollows_ping = nofollows_ping(&mut glean);
    let manual_ping = manual_ping(&mut glean);

    glean.set_ping_enabled(&nofollows_ping, true);

    // We need to store a metric as an empty ping is not stored.
    let counter = CounterMetric::new(CommonMetricData {
        name: "counter".into(),
        category: "local".into(),
        send_in_pings: vec!["manual".into(), "nofollows".into()],
        lifetime: Lifetime::Application,
        ..Default::default()
    });

    // Trigger a ping with data.
    counter.add_sync(&glean, 1);
    assert!(manual_ping.submit_sync(&glean, None));
    assert!(nofollows_ping.submit_sync(&glean, None));
    assert_eq!(2, get_queued_pings(glean.get_data_path()).unwrap().len());

    glean.set_upload_enabled(false);
    assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());

    // Ping is still there after a Glean restart
    let (glean, _t) = new_glean(Some(t));
    assert_eq!(1, get_queued_pings(glean.get_data_path()).unwrap().len());
}
