// Copyright (C) 2026, Cloudflare, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use super::*;
use crate::events::quic::MetricsUpdated;
use crate::events::quic::PacketSent;
use crate::events::quic::PacketType;
use crate::events::quic::QuicFrame;
use crate::events::EventData;
use crate::events::ExData;
use crate::events::RawInfo;
use crate::Event;

#[test]
fn packet_sent_event_no_frames() {
    let log_string = r#"{
  "time": 0.0,
  "name": "transport:packet_sent",
  "data": {
    "header": {
      "packet_type": "initial",
      "packet_number": 0,
      "version": "1",
      "scil": 8,
      "dcil": 8,
      "scid": "7e37e4dcc6682da8",
      "dcid": "36ce104eee50101c"
    },
    "raw": {
      "length": 1251,
      "payload_length": 1224
    }
  }
}"#;

    let pkt_hdr = make_pkt_hdr(PacketType::Initial);
    let ev_data = EventData::PacketSent(PacketSent {
        header: pkt_hdr,
        raw: Some(RawInfo {
            length: Some(1251),
            payload_length: Some(1224),
            data: None,
        }),
        ..Default::default()
    });

    let ev = Event::with_time(0.0, ev_data);

    assert_eq!(serde_json::to_string_pretty(&ev).unwrap(), log_string);
}

#[test]
fn packet_sent_event_some_frames() {
    let log_string = r#"{
  "time": 0.0,
  "name": "transport:packet_sent",
  "data": {
    "header": {
      "packet_type": "initial",
      "packet_number": 0,
      "version": "1",
      "scil": 8,
      "dcil": 8,
      "scid": "7e37e4dcc6682da8",
      "dcid": "36ce104eee50101c"
    },
    "raw": {
      "length": 1251,
      "payload_length": 1224
    },
    "frames": [
      {
        "frame_type": "padding",
        "payload_length": 1234
      },
      {
        "frame_type": "ping"
      },
      {
        "frame_type": "stream",
        "stream_id": 0,
        "offset": 0,
        "length": 100,
        "fin": true
      }
    ]
  }
}"#;

    let pkt_hdr = make_pkt_hdr(PacketType::Initial);

    let frames = vec![
        QuicFrame::Padding {
            payload_length: 1234,
            length: None,
        },
        QuicFrame::Ping {
            payload_length: None,
            length: None,
        },
        QuicFrame::Stream {
            stream_id: 0,
            offset: 0,
            length: 100,
            fin: Some(true),
            raw: None,
        },
    ];

    let ev_data = EventData::PacketSent(PacketSent {
        header: pkt_hdr,
        frames: Some(frames.into()),
        raw: Some(RawInfo {
            length: Some(1251),
            payload_length: Some(1224),
            data: None,
        }),
        ..Default::default()
    });

    let ev = Event::with_time(0.0, ev_data);
    assert_eq!(serde_json::to_string_pretty(&ev).unwrap(), log_string);
}

// Test constants for MetricsUpdated tests
const MIN_RTT: f32 = 10.0;
const SMOOTHED_RTT: f32 = 15.0;
const CONGESTION_WINDOW: u64 = 12000;
const PACING_RATE: u64 = 500000;
const DELIVERY_RATE: u64 = 1000000;
const COLLISION_VALUE: f32 = 999.0;

#[test]
fn packet_header() {
    let pkt_hdr = make_pkt_hdr(PacketType::Initial);

    let log_string = r#"{
  "packet_type": "initial",
  "packet_number": 0,
  "version": "1",
  "scil": 8,
  "dcil": 8,
  "scid": "7e37e4dcc6682da8",
  "dcid": "36ce104eee50101c"
}"#;

    assert_eq!(serde_json::to_string_pretty(&pkt_hdr).unwrap(), log_string);
}

#[test]
fn metrics_updated_with_ex_data() {
    // Test that ex_data fields are flattened into the same object
    let ex_data = ExData::from([(
        "delivery_rate".to_string(),
        serde_json::json!(DELIVERY_RATE),
    )]);

    let metrics = MetricsUpdated {
        min_rtt: Some(MIN_RTT),
        congestion_window: Some(CONGESTION_WINDOW),
        ex_data,
        ..Default::default()
    };

    let json = serde_json::to_value(&metrics).unwrap();

    // Verify standard fields are present
    assert_eq!(json["min_rtt"], MIN_RTT);
    assert_eq!(json["congestion_window"], CONGESTION_WINDOW);

    // Verify ex_data field is flattened (not nested under "ex_data")
    assert_eq!(json["delivery_rate"], DELIVERY_RATE);
    assert!(json.get("ex_data").is_none(), "ex_data should be flattened");
}

#[test]
fn metrics_updated_ex_data_collision() {
    // Test collision: same field set via struct AND ex_data.
    // With serde's preserve_order feature and ex_data at the top of the
    // struct, standard fields are serialized last and take precedence.

    let ex_data = ExData::from([(
        "min_rtt".to_string(),
        serde_json::json!(COLLISION_VALUE),
    )]);

    let metrics = MetricsUpdated {
        min_rtt: Some(MIN_RTT), // struct field value
        ex_data,                // ex_data also has min_rtt
        ..Default::default()
    };

    let json = serde_json::to_value(&metrics).unwrap();

    // Standard field wins in collision - ex_data cannot overwrite standard
    // fields, which prevents accidental data corruption.
    assert_eq!(json["min_rtt"], MIN_RTT);
}

#[test]
fn metrics_updated_round_trip() {
    // Test serialization -> deserialization round-trip
    let ex_data = ExData::from([(
        "delivery_rate".to_string(),
        serde_json::json!(DELIVERY_RATE),
    )]);

    let original = MetricsUpdated {
        min_rtt: Some(MIN_RTT),
        smoothed_rtt: Some(SMOOTHED_RTT),
        congestion_window: Some(CONGESTION_WINDOW),
        pacing_rate: Some(PACING_RATE),
        ex_data,
        ..Default::default()
    };

    let json_str = serde_json::to_string(&original).unwrap();
    let deserialized: MetricsUpdated = serde_json::from_str(&json_str).unwrap();

    // Standard fields round-trip correctly
    assert_eq!(deserialized.min_rtt, original.min_rtt);
    assert_eq!(deserialized.smoothed_rtt, original.smoothed_rtt);
    assert_eq!(deserialized.congestion_window, original.congestion_window);
    assert_eq!(deserialized.pacing_rate, original.pacing_rate);

    // ex_data fields round-trip correctly
    assert_eq!(
        deserialized.ex_data.get("delivery_rate"),
        Some(&serde_json::json!(DELIVERY_RATE))
    );
}

#[test]
fn metrics_updated_no_ex_data() {
    // Test that ex_data is not present when not used
    let metrics = MetricsUpdated {
        min_rtt: Some(MIN_RTT),
        congestion_window: Some(CONGESTION_WINDOW),
        ..Default::default()
    };

    let json = serde_json::to_value(&metrics).unwrap();

    // Verify standard fields are present
    assert_eq!(json["min_rtt"], MIN_RTT);
    assert_eq!(json["congestion_window"], CONGESTION_WINDOW);

    // Verify ex_data is not present
    assert!(
        json.get("ex_data").is_none(),
        "ex_data should not be present"
    );
}
