package utils

import (
	"testing"
	"time"

	"github.com/quic-go/quic-go/internal/protocol"
	"github.com/stretchr/testify/require"
)

func TestRTTStatsDefaults(t *testing.T) {
	rttStats := NewRTTStats()
	require.False(t, rttStats.HasMeasurement())
	require.Equal(t, DefaultInitialRTT, rttStats.MinRTT())
	require.Equal(t, DefaultInitialRTT, rttStats.SmoothedRTT())
}

func TestRTTStatsSmoothedRTT(t *testing.T) {
	rttStats := NewRTTStats()
	require.False(t, rttStats.HasMeasurement())
	// verify that ack_delay is ignored in the first measurement
	rttStats.UpdateRTT(300*time.Millisecond, 100*time.Millisecond)
	require.True(t, rttStats.HasMeasurement())
	require.Equal(t, 300*time.Millisecond, rttStats.LatestRTT())
	require.Equal(t, 300*time.Millisecond, rttStats.SmoothedRTT())
	// verify that smoothed RTT includes max ack delay if it's reasonable
	rttStats.UpdateRTT(350*time.Millisecond, 50*time.Millisecond)
	require.Equal(t, 300*time.Millisecond, rttStats.LatestRTT())
	require.Equal(t, 300*time.Millisecond, rttStats.SmoothedRTT())
	// verify that large erroneous ack_delay does not change smoothed RTT
	rttStats.UpdateRTT(200*time.Millisecond, 300*time.Millisecond)
	require.Equal(t, 200*time.Millisecond, rttStats.LatestRTT())
	require.Equal(t, 287500*time.Microsecond, rttStats.SmoothedRTT())
}

func TestRTTStatsMinRTT(t *testing.T) {
	rttStats := NewRTTStats()
	rttStats.UpdateRTT(200*time.Millisecond, 0)
	require.Equal(t, 200*time.Millisecond, rttStats.MinRTT())
	rttStats.UpdateRTT(10*time.Millisecond, 0)
	require.Equal(t, 10*time.Millisecond, rttStats.MinRTT())
	rttStats.UpdateRTT(50*time.Millisecond, 0)
	require.Equal(t, 10*time.Millisecond, rttStats.MinRTT())
	rttStats.UpdateRTT(50*time.Millisecond, 0)
	require.Equal(t, 10*time.Millisecond, rttStats.MinRTT())
	rttStats.UpdateRTT(50*time.Millisecond, 0)
	require.Equal(t, 10*time.Millisecond, rttStats.MinRTT())
	// verify that ack_delay does not go into recording of MinRTT
	rttStats.UpdateRTT(7*time.Millisecond, 2*time.Millisecond)
	require.Equal(t, 7*time.Millisecond, rttStats.MinRTT())
}

func TestRTTStatsMaxAckDelay(t *testing.T) {
	rttStats := NewRTTStats()
	rttStats.SetMaxAckDelay(42 * time.Minute)
	require.Equal(t, 42*time.Minute, rttStats.MaxAckDelay())
}

func TestRTTStatsComputePTO(t *testing.T) {
	const (
		maxAckDelay = 42 * time.Minute
		rtt         = time.Second
	)
	rttStats := NewRTTStats()
	rttStats.SetMaxAckDelay(maxAckDelay)
	rttStats.UpdateRTT(rtt, 0)
	require.Equal(t, rtt, rttStats.SmoothedRTT())
	require.Equal(t, rtt/2, rttStats.MeanDeviation())
	require.Equal(t, rtt+4*(rtt/2), rttStats.PTO(false))
	require.Equal(t, rtt+4*(rtt/2)+maxAckDelay, rttStats.PTO(true))
}

func TestRTTStatsPTOWithShortRTT(t *testing.T) {
	const rtt = time.Microsecond
	rttStats := NewRTTStats()
	rttStats.UpdateRTT(rtt, 0)
	require.Equal(t, rtt+protocol.TimerGranularity, rttStats.PTO(true))
}

func TestRTTStatsUpdateWithBadSendDeltas(t *testing.T) {
	rttStats := NewRTTStats()
	const initialRtt = 10 * time.Millisecond
	rttStats.UpdateRTT(initialRtt, 0)
	require.Equal(t, initialRtt, rttStats.MinRTT())
	require.Equal(t, initialRtt, rttStats.SmoothedRTT())

	badSendDeltas := []time.Duration{
		0,
		-1000 * time.Microsecond,
	}

	for _, badSendDelta := range badSendDeltas {
		rttStats.UpdateRTT(badSendDelta, 0)
		require.Equal(t, initialRtt, rttStats.MinRTT())
		require.Equal(t, initialRtt, rttStats.SmoothedRTT())
	}
}

func TestRTTStatsRestore(t *testing.T) {
	rttStats := NewRTTStats()
	rttStats.SetInitialRTT(10 * time.Second)
	require.Equal(t, 10*time.Second, rttStats.LatestRTT())
	require.Equal(t, 10*time.Second, rttStats.SmoothedRTT())
	require.Zero(t, rttStats.MeanDeviation())
	// update the RTT and make sure that the initial value is immediately forgotten
	rttStats.UpdateRTT(200*time.Millisecond, 0)
	require.Equal(t, 200*time.Millisecond, rttStats.LatestRTT())
	require.Equal(t, 200*time.Millisecond, rttStats.SmoothedRTT())
	require.Equal(t, 100*time.Millisecond, rttStats.MeanDeviation())
}

func TestRTTMeasurementAfterRestore(t *testing.T) {
	rttStats := NewRTTStats()
	const rtt = 10 * time.Millisecond
	rttStats.UpdateRTT(rtt, 0)
	require.Equal(t, rtt, rttStats.LatestRTT())
	require.Equal(t, rtt, rttStats.SmoothedRTT())
	rttStats.SetInitialRTT(time.Minute)
	require.Equal(t, rtt, rttStats.LatestRTT())
	require.Equal(t, rtt, rttStats.SmoothedRTT())
}

func TestRTTStatsResetForPathMigration(t *testing.T) {
	rttStats := NewRTTStats()
	rttStats.SetMaxAckDelay(42 * time.Millisecond)
	rttStats.UpdateRTT(time.Second, 0)
	rttStats.UpdateRTT(10*time.Second, 0)
	require.True(t, rttStats.HasMeasurement())
	require.Equal(t, time.Second, rttStats.MinRTT())
	require.Equal(t, 10*time.Second, rttStats.LatestRTT())
	require.NotZero(t, rttStats.SmoothedRTT())

	rttStats.ResetForPathMigration()
	require.False(t, rttStats.HasMeasurement())
	require.Equal(t, DefaultInitialRTT, rttStats.MinRTT())
	require.Equal(t, DefaultInitialRTT, rttStats.LatestRTT())
	require.Equal(t, DefaultInitialRTT, rttStats.SmoothedRTT())
	require.Equal(t, 2*DefaultInitialRTT, rttStats.PTO(false))
	// make sure that max_ack_delay was not reset
	require.Equal(t, 42*time.Millisecond, rttStats.MaxAckDelay())

	rttStats.UpdateRTT(10*time.Millisecond, 0)
	require.True(t, rttStats.HasMeasurement())
	require.Equal(t, 10*time.Millisecond, rttStats.SmoothedRTT())
	require.Equal(t, 10*time.Millisecond, rttStats.LatestRTT())
}
