package utils

import (
	"time"

	"github.com/quic-go/quic-go/internal/protocol"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("RTT stats", func() {
	var rttStats *RTTStats

	BeforeEach(func() {
		rttStats = NewRTTStats()
	})

	It("DefaultsBeforeUpdate", func() {
		Expect(rttStats.MinRTT()).To(Equal(time.Duration(0)))
		Expect(rttStats.SmoothedRTT()).To(Equal(time.Duration(0)))
	})

	It("SmoothedRTT", func() {
		// Verify that ack_delay is ignored in the first measurement.
		rttStats.UpdateRTT((300 * time.Millisecond), (100 * time.Millisecond), time.Time{})
		Expect(rttStats.LatestRTT()).To(Equal((300 * time.Millisecond)))
		Expect(rttStats.SmoothedRTT()).To(Equal((300 * time.Millisecond)))
		// Verify that Smoothed RTT includes max ack delay if it's reasonable.
		rttStats.UpdateRTT((350 * time.Millisecond), (50 * time.Millisecond), time.Time{})
		Expect(rttStats.LatestRTT()).To(Equal((300 * time.Millisecond)))
		Expect(rttStats.SmoothedRTT()).To(Equal((300 * time.Millisecond)))
		// Verify that large erroneous ack_delay does not change Smoothed RTT.
		rttStats.UpdateRTT((200 * time.Millisecond), (300 * time.Millisecond), time.Time{})
		Expect(rttStats.LatestRTT()).To(Equal((200 * time.Millisecond)))
		Expect(rttStats.SmoothedRTT()).To(Equal((287500 * time.Microsecond)))
	})

	It("MinRTT", func() {
		rttStats.UpdateRTT((200 * time.Millisecond), 0, time.Time{})
		Expect(rttStats.MinRTT()).To(Equal((200 * time.Millisecond)))
		rttStats.UpdateRTT((10 * time.Millisecond), 0, time.Time{}.Add((10 * time.Millisecond)))
		Expect(rttStats.MinRTT()).To(Equal((10 * time.Millisecond)))
		rttStats.UpdateRTT((50 * time.Millisecond), 0, time.Time{}.Add((20 * time.Millisecond)))
		Expect(rttStats.MinRTT()).To(Equal((10 * time.Millisecond)))
		rttStats.UpdateRTT((50 * time.Millisecond), 0, time.Time{}.Add((30 * time.Millisecond)))
		Expect(rttStats.MinRTT()).To(Equal((10 * time.Millisecond)))
		rttStats.UpdateRTT((50 * time.Millisecond), 0, time.Time{}.Add((40 * time.Millisecond)))
		Expect(rttStats.MinRTT()).To(Equal((10 * time.Millisecond)))
		// Verify that ack_delay does not go into recording of MinRTT_.
		rttStats.UpdateRTT((7 * time.Millisecond), (2 * time.Millisecond), time.Time{}.Add((50 * time.Millisecond)))
		Expect(rttStats.MinRTT()).To(Equal((7 * time.Millisecond)))
	})

	It("MaxAckDelay", func() {
		rttStats.SetMaxAckDelay(42 * time.Minute)
		Expect(rttStats.MaxAckDelay()).To(Equal(42 * time.Minute))
	})

	It("computes the PTO", func() {
		maxAckDelay := 42 * time.Minute
		rttStats.SetMaxAckDelay(maxAckDelay)
		rtt := time.Second
		rttStats.UpdateRTT(rtt, 0, time.Time{})
		Expect(rttStats.SmoothedRTT()).To(Equal(rtt))
		Expect(rttStats.MeanDeviation()).To(Equal(rtt / 2))
		Expect(rttStats.PTO(false)).To(Equal(rtt + 4*(rtt/2)))
		Expect(rttStats.PTO(true)).To(Equal(rtt + 4*(rtt/2) + maxAckDelay))
	})

	It("uses the granularity for computing the PTO for short RTTs", func() {
		rtt := time.Microsecond
		rttStats.UpdateRTT(rtt, 0, time.Time{})
		Expect(rttStats.PTO(true)).To(Equal(rtt + protocol.TimerGranularity))
	})

	It("ExpireSmoothedMetrics", func() {
		initialRtt := (10 * time.Millisecond)
		rttStats.UpdateRTT(initialRtt, 0, time.Time{})
		Expect(rttStats.MinRTT()).To(Equal(initialRtt))
		Expect(rttStats.SmoothedRTT()).To(Equal(initialRtt))

		Expect(rttStats.MeanDeviation()).To(Equal(initialRtt / 2))

		// Update once with a 20ms RTT.
		doubledRtt := initialRtt * (2)
		rttStats.UpdateRTT(doubledRtt, 0, time.Time{})
		Expect(rttStats.SmoothedRTT()).To(Equal(time.Duration(float32(initialRtt) * 1.125)))

		// Expire the smoothed metrics, increasing smoothed rtt and mean deviation.
		rttStats.ExpireSmoothedMetrics()
		Expect(rttStats.SmoothedRTT()).To(Equal(doubledRtt))
		Expect(rttStats.MeanDeviation()).To(Equal(time.Duration(float32(initialRtt) * 0.875)))

		// Now go back down to 5ms and expire the smoothed metrics, and ensure the
		// mean deviation increases to 15ms.
		halfRtt := initialRtt / 2
		rttStats.UpdateRTT(halfRtt, 0, time.Time{})
		Expect(doubledRtt).To(BeNumerically(">", rttStats.SmoothedRTT()))
		Expect(initialRtt).To(BeNumerically("<", rttStats.MeanDeviation()))
	})

	It("UpdateRTTWithBadSendDeltas", func() {
		// Make sure we ignore bad RTTs.
		// base::test::MockLog log;

		initialRtt := (10 * time.Millisecond)
		rttStats.UpdateRTT(initialRtt, 0, time.Time{})
		Expect(rttStats.MinRTT()).To(Equal(initialRtt))
		Expect(rttStats.SmoothedRTT()).To(Equal(initialRtt))

		badSendDeltas := []time.Duration{
			0,
			InfDuration,
			-1000 * time.Microsecond,
		}
		// log.StartCapturingLogs();

		for _, badSendDelta := range badSendDeltas {
			// SCOPED_TRACE(Message() << "bad_send_delta = "
			//  << bad_send_delta.ToMicroseconds());
			// EXPECT_CALL(log, Log(LOG_WARNING, _, _, _, HasSubstr("Ignoring")));
			rttStats.UpdateRTT(badSendDelta, 0, time.Time{})
			Expect(rttStats.MinRTT()).To(Equal(initialRtt))
			Expect(rttStats.SmoothedRTT()).To(Equal(initialRtt))
		}
	})

	It("ResetAfterConnectionMigrations", func() {
		rttStats.UpdateRTT(200*time.Millisecond, 0, time.Time{})
		Expect(rttStats.LatestRTT()).To(Equal((200 * time.Millisecond)))
		Expect(rttStats.SmoothedRTT()).To(Equal((200 * time.Millisecond)))
		Expect(rttStats.MinRTT()).To(Equal((200 * time.Millisecond)))
		rttStats.UpdateRTT((300 * time.Millisecond), (100 * time.Millisecond), time.Time{})
		Expect(rttStats.LatestRTT()).To(Equal((200 * time.Millisecond)))
		Expect(rttStats.SmoothedRTT()).To(Equal((200 * time.Millisecond)))
		Expect(rttStats.MinRTT()).To(Equal((200 * time.Millisecond)))

		// Reset rtt stats on connection migrations.
		rttStats.OnConnectionMigration()
		Expect(rttStats.LatestRTT()).To(Equal(time.Duration(0)))
		Expect(rttStats.SmoothedRTT()).To(Equal(time.Duration(0)))
		Expect(rttStats.MinRTT()).To(Equal(time.Duration(0)))
	})

	It("restores the RTT", func() {
		rttStats.SetInitialRTT(10 * time.Second)
		Expect(rttStats.LatestRTT()).To(Equal(10 * time.Second))
		Expect(rttStats.SmoothedRTT()).To(Equal(10 * time.Second))
		Expect(rttStats.MeanDeviation()).To(BeZero())
		// update the RTT and make sure that the initial value is immediately forgotten
		rttStats.UpdateRTT(200*time.Millisecond, 0, time.Time{})
		Expect(rttStats.LatestRTT()).To(Equal(200 * time.Millisecond))
		Expect(rttStats.SmoothedRTT()).To(Equal(200 * time.Millisecond))
		Expect(rttStats.MeanDeviation()).To(Equal(100 * time.Millisecond))
	})

	It("doesn't restore the RTT if we already have a measurement", func() {
		const rtt = 10 * time.Millisecond
		rttStats.UpdateRTT(rtt, 0, time.Now())
		Expect(rttStats.LatestRTT()).To(Equal(rtt))
		Expect(rttStats.SmoothedRTT()).To(Equal(rtt))
		rttStats.SetInitialRTT(time.Minute)
		Expect(rttStats.LatestRTT()).To(Equal(rtt))
		Expect(rttStats.SmoothedRTT()).To(Equal(rtt))
	})
})
