/*
 *  Copyright (c) 2025 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "video/timing/simulator/rtc_event_log_driver.h"

#include <cstdint>
#include <memory>
#include <utility>

#include "absl/strings/string_view.h"
#include "api/environment/environment.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "logging/rtc_event_log/rtc_event_log_parser.h"
#include "modules/rtp_rtcp/source/ntp_time_util.h"
#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h"
#include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h"
#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h"
#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h"
#include "modules/rtp_rtcp/source/rtcp_packet/rrtr.h"
#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h"
#include "rtc_base/checks.h"
#include "system_wrappers/include/ntp_time.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "video/timing/simulator/rtp_packet_simulator.h"
#include "video/timing/simulator/test/parsed_rtc_event_log_builder.h"

namespace webrtc::video_timing_simulator {
namespace {

using ::testing::Eq;
using ::testing::Field;

constexpr absl::string_view kEmptyFieldTrialsString = "";

constexpr uint32_t kSsrc1 = 123456;
constexpr uint32_t kRtxSsrc1 = 234567;
constexpr uint32_t kSsrc2 = 345678;
constexpr uint32_t kRtxSsrc2 = 456789;

constexpr uint16_t kRtxOsn = 823;

constexpr uint32_t kSenderSsrc = 123456;
constexpr uint32_t kReceiverSsrc = 987654;

class MockRtcEventLogDriverStream : public RtcEventLogDriver::StreamInterface {
 public:
  MOCK_METHOD(void,
              InsertSimulatedPacket,
              (const RtpPacketSimulator::SimulatedPacket& simulated_packet),
              (override));
  MOCK_METHOD(void, UpdateMaxRtt, (TimeDelta max_rtt), (override));
  MOCK_METHOD(void, Close, (), (override));
};

class MockRtcEventLogDriverStreamFactory {
 public:
  MockRtcEventLogDriverStreamFactory()
      : stream1_(std::make_unique<MockRtcEventLogDriverStream>()),
        stream2_(std::make_unique<MockRtcEventLogDriverStream>()),
        stream1_ptr_(stream1_.get()),
        stream2_ptr_(stream2_.get()) {}
  ~MockRtcEventLogDriverStreamFactory() = default;

  std::unique_ptr<MockRtcEventLogDriverStream> Create(Environment,
                                                      uint32_t ssrc) {
    if (ssrc == kSsrc1) {
      RTC_CHECK(stream1_) << "Stream 1 was already moved";
      return std::move(stream1_);
    }
    if (ssrc == kSsrc2) {
      RTC_CHECK(stream2_) << "Stream 2 was already moved";
      return std::move(stream2_);
    }
    RTC_CHECK_NOTREACHED();
    return std::make_unique<MockRtcEventLogDriverStream>();
  }

  int NumStreamsCreated() const {
    return static_cast<int>(stream1_ == nullptr) +
           static_cast<int>(stream2_ == nullptr);
  }

  // Unique pointers for ownership.
  std::unique_ptr<MockRtcEventLogDriverStream> stream1_;
  std::unique_ptr<MockRtcEventLogDriverStream> stream2_;

  // Raw pointers for expectations.
  MockRtcEventLogDriverStream* stream1_ptr_;
  MockRtcEventLogDriverStream* stream2_ptr_;
};

class RtcEventLogDriverTest : public ::testing::Test {
 protected:
  auto BuildStreamFactory() {
    return [this](Environment env, uint32_t ssrc, uint32_t rtx_ssrc) {
      return stream_factory_.Create(env, ssrc);
    };
  }

  MockRtcEventLogDriverStreamFactory stream_factory_;
  ParsedRtcEventLogBuilder parsed_log_builder_;
};

TEST_F(RtcEventLogDriverTest, EmptyLogDoesNotCreateStreams) {
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();

  EXPECT_EQ(stream_factory_.NumStreamsCreated(), 0);
}

TEST_F(RtcEventLogDriverTest, LoggedVideoRecvConfigCreatesStream) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
  driver.Simulate();

  EXPECT_EQ(stream_factory_.NumStreamsCreated(), 1);
}

TEST_F(RtcEventLogDriverTest,
       LoggedVideoRecvConfigCreatesStreamIfIncludedInSsrcFilter) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  RtcEventLogDriver driver(RtcEventLogDriver::Config{.ssrc_filter = {kSsrc1}},
                           parsed_log.get(), kEmptyFieldTrialsString,
                           BuildStreamFactory());
  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
  driver.Simulate();

  EXPECT_EQ(stream_factory_.NumStreamsCreated(), 1);
}

TEST_F(RtcEventLogDriverTest,
       LoggedVideoRecvConfigDoesNotCreateStreamIfNotIncludedInSsrcFilter) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  RtcEventLogDriver driver(RtcEventLogDriver::Config{.ssrc_filter = {kSsrc2}},
                           parsed_log.get(), kEmptyFieldTrialsString,
                           BuildStreamFactory());
  driver.Simulate();

  EXPECT_EQ(stream_factory_.NumStreamsCreated(), 0);
}

TEST_F(RtcEventLogDriverTest, LoggedVideoRecvConfigsCreateStreams) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  parsed_log_builder_.LogVideoRecvConfig(kSsrc2, kRtxSsrc2);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
  EXPECT_CALL(*stream_factory_.stream2_ptr_, Close());
  driver.Simulate();

  EXPECT_EQ(stream_factory_.NumStreamsCreated(), 2);
}

class CountingRtcEventLogDriverStreamFactoryFactory {
 public:
  auto BuildStreamFactory() {
    return [this](Environment env, uint32_t ssrc, uint32_t rtx_ssrc) {
      ++num_streams_created_;
      return std::make_unique<MockRtcEventLogDriverStream>();
    };
  }

  int NumStreamsCreated() const { return num_streams_created_; }

 private:
  int num_streams_created_ = 0;
};

TEST_F(RtcEventLogDriverTest, ReusesStreamIfConfigured) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  CountingRtcEventLogDriverStreamFactoryFactory counter;
  RtcEventLogDriver driver(RtcEventLogDriver::Config{.reuse_streams = true},
                           parsed_log.get(), kEmptyFieldTrialsString,
                           counter.BuildStreamFactory());
  driver.Simulate();

  EXPECT_EQ(counter.NumStreamsCreated(), 1);
}

TEST_F(RtcEventLogDriverTest, DoesNotReuseStreamIfConfigured) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  CountingRtcEventLogDriverStreamFactoryFactory counter;
  RtcEventLogDriver driver(RtcEventLogDriver::Config{.reuse_streams = false},
                           parsed_log.get(), kEmptyFieldTrialsString,
                           counter.BuildStreamFactory());
  driver.Simulate();

  EXPECT_EQ(counter.NumStreamsCreated(), 2);
}

TEST_F(RtcEventLogDriverTest, FirstLoggedEventSetsSimulationClock) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();

  EXPECT_EQ(driver.GetCurrentTimeForTesting(),
            parsed_log_builder_.CurrentTime() +
                RtcEventLogDriver::kShutdownAdvanceTimeSlack);
}

TEST_F(RtcEventLogDriverTest, LoggedEventAdvancesSimulationClock) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  parsed_log_builder_.AdvanceTime(TimeDelta::Millis(50));
  parsed_log_builder_.LogVideoRecvConfig(kSsrc2, kRtxSsrc2);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();

  EXPECT_EQ(driver.GetCurrentTimeForTesting(),
            parsed_log_builder_.CurrentTime() +
                RtcEventLogDriver::kShutdownAdvanceTimeSlack);
}

TEST_F(RtcEventLogDriverTest, LoggedRtpPacketIncomingInsertsPacketIntoStream) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  parsed_log_builder_.LogRtpPacketIncoming(kSsrc1);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  EXPECT_CALL(*stream_factory_.stream1_ptr_, InsertSimulatedPacket);
  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();
}

TEST_F(RtcEventLogDriverTest,
       LoggedRtpPacketIncomingInsertsRtxPacketIntoStream) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  parsed_log_builder_.LogRtpPacketIncoming(kRtxSsrc1, kRtxOsn);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  EXPECT_CALL(
      *stream_factory_.stream1_ptr_,
      InsertSimulatedPacket(
          Field(&RtpPacketSimulator::SimulatedPacket::has_rtx_osn, Eq(true))));
  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();
}

TEST_F(RtcEventLogDriverTest,
       LoggedRtpPacketIncomingsInsertsPacketsIntoStreams) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
  parsed_log_builder_.LogVideoRecvConfig(kSsrc2, kRtxSsrc2);
  parsed_log_builder_.LogRtpPacketIncoming(kSsrc1);
  parsed_log_builder_.LogRtpPacketIncoming(kSsrc2);
  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  EXPECT_CALL(*stream_factory_.stream1_ptr_, InsertSimulatedPacket);
  EXPECT_CALL(*stream_factory_.stream2_ptr_, InsertSimulatedPacket);
  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();
}

rtcp::SenderReport CreateSenderReport(NtpTime ntp) {
  rtcp::SenderReport sr;
  sr.SetSenderSsrc(kSenderSsrc);
  sr.SetNtp(ntp);
  return sr;
}

rtcp::SenderReport CreateSenderReportWithReportBlock(
    NtpTime ntp,
    uint32_t last_sr,
    uint32_t delay_since_last_sr) {
  rtcp::SenderReport sr;
  sr.SetSenderSsrc(kReceiverSsrc);
  sr.SetNtp(ntp);
  rtcp::ReportBlock block;
  block.SetMediaSsrc(kSenderSsrc);
  block.SetLastSr(last_sr);
  block.SetDelayLastSr(delay_since_last_sr);
  sr.AddReportBlock(block);
  return sr;
}

rtcp::ReceiverReport CreateReceiverReport(uint32_t last_sr,
                                          uint32_t delay_since_last_sr) {
  rtcp::ReceiverReport rr;
  rr.SetSenderSsrc(kReceiverSsrc);
  rtcp::ReportBlock block;
  block.SetMediaSsrc(kSenderSsrc);
  block.SetLastSr(last_sr);
  block.SetDelayLastSr(delay_since_last_sr);
  rr.AddReportBlock(block);
  return rr;
}

rtcp::ExtendedReports CreateExtendedReportsWithRrtr(NtpTime ntp) {
  rtcp::ExtendedReports xr;
  xr.SetSenderSsrc(kReceiverSsrc);
  rtcp::Rrtr rrtr;
  rrtr.SetNtp(ntp);
  xr.SetRrtr(rrtr);
  return xr;
}

rtcp::ExtendedReports CreateExtendedReportsWithDlrr(
    uint32_t last_rr,
    uint32_t delay_since_last_rr) {
  rtcp::ExtendedReports xr;
  xr.SetSenderSsrc(kSenderSsrc);
  rtcp::ReceiveTimeInfo dlrr_block;
  dlrr_block.ssrc = kReceiverSsrc;
  dlrr_block.last_rr = last_rr;
  dlrr_block.delay_since_last_rr = delay_since_last_rr;
  xr.AddDlrrItem(dlrr_block);
  return xr;
}

TEST_F(RtcEventLogDriverTest, SenderCalculatesRttFromIncomingRr) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);

  // Outgoing SR.
  NtpTime ntp = parsed_log_builder_.CurrentNtpTime();
  parsed_log_builder_.LogRtcpPacketOutgoing(CreateSenderReport(ntp));

  // Incoming RR: 50ms delay, arrives after 150ms => RTT is 100ms.
  parsed_log_builder_.AdvanceTime(TimeDelta::Millis(150));
  uint32_t last_sr = CompactNtp(ntp);
  uint32_t delay_since_last_sr = SaturatedToCompactNtp(TimeDelta::Millis(50));
  parsed_log_builder_.LogRtcpPacketIncoming(
      CreateReceiverReport(last_sr, delay_since_last_sr));

  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  EXPECT_CALL(*stream_factory_.stream1_ptr_,
              UpdateMaxRtt(TimeDelta::Millis(100)));
  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();
}

TEST_F(RtcEventLogDriverTest, SenderCalculatesRttFromIncomingSr) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);

  // Outgoing SR.
  NtpTime ntp = parsed_log_builder_.CurrentNtpTime();
  parsed_log_builder_.LogRtcpPacketOutgoing(CreateSenderReport(ntp));

  // Incoming SR: 50ms delay, arrives after 150ms => RTT is 100ms.
  parsed_log_builder_.AdvanceTime(TimeDelta::Millis(150));
  uint32_t last_sr = CompactNtp(ntp);
  uint32_t delay_since_last_sr = SaturatedToCompactNtp(TimeDelta::Millis(50));
  NtpTime ntp_incoming;
  parsed_log_builder_.LogRtcpPacketIncoming(CreateSenderReportWithReportBlock(
      ntp_incoming, last_sr, delay_since_last_sr));

  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  EXPECT_CALL(*stream_factory_.stream1_ptr_,
              UpdateMaxRtt(TimeDelta::Millis(100)));
  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();
}

TEST_F(RtcEventLogDriverTest, ReceiverCalculatesRttFromIncomingXr) {
  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);

  // Outgoing XR with RRTR.
  NtpTime ntp = parsed_log_builder_.CurrentNtpTime();
  parsed_log_builder_.LogRtcpPacketOutgoing(CreateExtendedReportsWithRrtr(ntp));

  // Incoming XR with DLRR: 50ms delay, arrives after 150ms => RTT is 100ms.
  parsed_log_builder_.AdvanceTime(TimeDelta::Millis(150));
  uint32_t last_rr = CompactNtp(ntp);
  uint32_t delay_since_last_rr = SaturatedToCompactNtp(TimeDelta::Millis(50));
  parsed_log_builder_.LogRtcpPacketIncoming(
      CreateExtendedReportsWithDlrr(last_rr, delay_since_last_rr));

  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();

  EXPECT_CALL(*stream_factory_.stream1_ptr_,
              UpdateMaxRtt(TimeDelta::Millis(100)));
  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
                           kEmptyFieldTrialsString, BuildStreamFactory());
  driver.Simulate();
}

}  // namespace
}  // namespace webrtc::video_timing_simulator
