// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package jitterbuffer

import (
	"math"
	"testing"

	"github.com/pion/rtp"
	"github.com/stretchr/testify/assert"
)

//nolint:cyclop,maintidx
func TestJitterBuffer(t *testing.T) {
	assert := assert.New(t)

	t.Run("Appends packets in order", func(*testing.T) {
		jb := New()
		assert.Equal(jb.lastSequence, uint16(0))
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5000, Timestamp: 500}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5001, Timestamp: 501}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5002, Timestamp: 502}, Payload: []byte{0x02}})

		assert.Equal(jb.lastSequence, uint16(5002))

		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5012, Timestamp: 512}, Payload: []byte{0x02}})

		assert.Equal(jb.stats.outOfOrderCount, uint32(1))
		assert.Equal(jb.packets.Length(), uint16(4))
		assert.Equal(jb.lastSequence, uint16(5012))
	})
	t.Run("Appends packets and wraps", func(*testing.T) {
		jb := New(WithMinimumPacketCount(1))
		assert.Equal(jb.lastSequence, uint16(0))
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 65535, Timestamp: 500}, Payload: []byte{0x02}})

		assert.Equal(jb.lastSequence, uint16(65535))

		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 0, Timestamp: 512}, Payload: []byte{0x02}})

		assert.Equal(jb.packets.Length(), uint16(2))
		assert.Equal(jb.lastSequence, uint16(0))

		head, err := jb.Pop()
		assert.Equal(head.SequenceNumber, uint16(65535))
		assert.Equal(err, nil)
		head, err = jb.Pop()
		assert.Equal(head.SequenceNumber, uint16(0))
		assert.Equal(err, nil)
	})

	t.Run("Appends packets and begins playout", func(*testing.T) {
		jb := New()
		for i := 0; i < 100; i++ {
			jb.Push(
				&rtp.Packet{
					Header: rtp.Header{
						SequenceNumber: uint16(5012 + i), //nolint:gosec // G115
						Timestamp:      uint32(512 + i),  //nolint:gosec // G115
					},
					Payload: []byte{0x02},
				},
			)
		}
		assert.Equal(jb.packets.Length(), uint16(100))
		assert.Equal(jb.state, Emitting)
		assert.Equal(jb.playoutHead, uint16(5012))
		head, err := jb.Pop()
		assert.Equal(head.SequenceNumber, uint16(5012))
		assert.Equal(err, nil)
	})
	t.Run("Appends packets and begins playout", func(*testing.T) {
		jb := New(WithMinimumPacketCount(1))
		events := make([]Event, 0)
		jb.Listen(BeginPlayback, func(event Event, _ *JitterBuffer) {
			events = append(events, event)
		})
		for i := 0; i < 2; i++ {
			//nolint:gosec // G115
			jb.Push(
				&rtp.Packet{
					Header: rtp.Header{
						SequenceNumber: uint16(5012 + i),
						Timestamp:      uint32(512 + i),
					},
					Payload: []byte{0x02},
				},
			)
		}
		assert.Equal(jb.packets.Length(), uint16(2))
		assert.Equal(jb.state, Emitting)
		assert.Equal(jb.playoutHead, uint16(5012))
		head, err := jb.Pop()
		assert.Equal(head.SequenceNumber, uint16(5012))
		assert.Equal(err, nil)
		assert.Equal(1, len(events))
		assert.Equal(Event(BeginPlayback), events[0])
	})

	t.Run("Wraps playout correctly", func(*testing.T) {
		jb := New()
		for i := 0; i < 100; i++ {
			sqnum := uint16(math.MaxUint16 - 32 + i) //nolint:gosec // G115
			//nolint:gosec // G115
			jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: sqnum, Timestamp: uint32(512 + i)}, Payload: []byte{0x02}})
		}
		assert.Equal(jb.packets.Length(), uint16(100))
		assert.Equal(jb.state, Emitting)
		assert.Equal(jb.playoutHead, uint16(math.MaxUint16-32))
		head, err := jb.Pop()
		assert.Equal(head.SequenceNumber, uint16(math.MaxUint16-32))
		assert.Equal(err, nil)
		for i := 0; i < 100; i++ {
			head, err := jb.Pop()
			if i < 99 {
				assert.Equal(head.SequenceNumber, uint16((math.MaxUint16 - 31 + i))) //nolint:gosec // G115
				assert.Equal(err, nil)
			} else {
				assert.Equal(head, (*rtp.Packet)(nil))
			}
		}
	})

	t.Run("Pops at timestamp correctly", func(*testing.T) {
		jb := New()
		for i := 0; i < 100; i++ {
			sqnum := uint16((math.MaxUint16 - 32 + i)) //nolint:gosec // G115
			//nolint:gosec // G115
			jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: sqnum, Timestamp: uint32(512 + i)}, Payload: []byte{0x02}})
		}
		assert.Equal(jb.packets.Length(), uint16(100))
		assert.Equal(jb.state, Emitting)
		head, err := jb.PopAtTimestamp(uint32(513))
		assert.Equal(head.SequenceNumber, uint16(math.MaxUint16-32+1))
		assert.Equal(err, nil)
		head, err = jb.PopAtTimestamp(uint32(513))
		assert.Equal(head, (*rtp.Packet)(nil))
		assert.NotEqual(err, nil)

		head, err = jb.Pop()
		assert.Equal(head.SequenceNumber, uint16(math.MaxUint16-32))
		assert.Equal(err, nil)
	})

	t.Run("Can peek at a packet", func(*testing.T) {
		jb := New()
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5000, Timestamp: 500}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5001, Timestamp: 501}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5002, Timestamp: 502}, Payload: []byte{0x02}})
		pkt, err := jb.Peek(false)
		assert.Equal(pkt.SequenceNumber, uint16(5002))
		assert.Equal(err, nil)
		for i := 0; i < 100; i++ {
			sqnum := uint16((math.MaxUint16 - 32 + i)) //nolint:gosec // G115
			//nolint:gosec // G115
			jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: sqnum, Timestamp: uint32(512 + i)}, Payload: []byte{0x02}})
		}
		pkt, err = jb.Peek(true)
		assert.Equal(pkt.SequenceNumber, uint16(5000))
		assert.Equal(err, nil)
	})

	t.Run("Pops at sequence with an invalid sequence number", func(*testing.T) {
		jb := New()
		for i := 0; i < 50; i++ {
			sqnum := uint16((math.MaxUint16 - 32 + i)) //nolint:gosec // G115
			//nolint:gosec // G115
			jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: sqnum, Timestamp: uint32(512 + i)}, Payload: []byte{0x02}})
		}
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 1019, Timestamp: uint32(9000)}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 1020, Timestamp: uint32(9000)}, Payload: []byte{0x02}})
		assert.Equal(jb.packets.Length(), uint16(52))
		assert.Equal(jb.state, Emitting)
		head, err := jb.PopAtSequence(uint16(9000))
		assert.Equal(head, (*rtp.Packet)(nil))
		assert.NotEqual(err, nil)
	})

	t.Run("Pops at timestamp with multiple packets", func(*testing.T) {
		jb := New()
		for i := 0; i < 50; i++ {
			sqnum := uint16((math.MaxUint16 - 32 + i)) //nolint:gosec // G115
			//nolint:gosec // G115
			jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: sqnum, Timestamp: uint32(512 + i)}, Payload: []byte{0x02}})
		}
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 1019, Timestamp: uint32(9000)}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 1020, Timestamp: uint32(9000)}, Payload: []byte{0x02}})
		assert.Equal(jb.packets.Length(), uint16(52))
		assert.Equal(jb.state, Emitting)
		head, err := jb.PopAtTimestamp(uint32(9000))
		assert.Equal(head.SequenceNumber, uint16(1019))
		assert.Equal(err, nil)
		head, err = jb.PopAtTimestamp(uint32(9000))
		assert.Equal(head.SequenceNumber, uint16(1020))
		assert.Equal(err, nil)

		head, err = jb.Pop()
		assert.Equal(head.SequenceNumber, uint16(math.MaxUint16-32))
		assert.Equal(err, nil)
	})

	t.Run("Peeks at timestamp with multiple packets", func(*testing.T) {
		jb := New()
		for i := 0; i < 50; i++ {
			sqnum := uint16((math.MaxUint16 - 32 + i)) //nolint:gosec // G115
			//nolint:gosec // G115
			jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: sqnum, Timestamp: uint32(512 + i)}, Payload: []byte{0x02}})
		}
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 1019, Timestamp: uint32(9000)}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 1020, Timestamp: uint32(9000)}, Payload: []byte{0x02}})
		assert.Equal(jb.packets.Length(), uint16(52))
		assert.Equal(jb.state, Emitting)
		head, err := jb.PeekAtSequence(uint16(1019))
		assert.Equal(head.SequenceNumber, uint16(1019))
		assert.Equal(err, nil)
		head, err = jb.PeekAtSequence(uint16(1020))
		assert.Equal(head.SequenceNumber, uint16(1020))
		assert.Equal(err, nil)

		head, err = jb.PopAtSequence(uint16(math.MaxUint16 - 32))
		assert.Equal(head.SequenceNumber, uint16(math.MaxUint16-32))
		assert.Equal(err, nil)
	})

	t.Run("SetPlayoutHead", func(*testing.T) {
		jb := New(WithMinimumPacketCount(1))

		// Push packets 0-9, but no packet 4
		for i := uint16(0); i < 10; i++ {
			if i == 4 {
				continue
			}
			jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: i, Timestamp: uint32(512 + i)}, Payload: []byte{0x00}})
		}

		// The first 3 packets will be able to popped
		for i := 0; i < 4; i++ {
			pkt, err := jb.Pop()
			assert.NoError(err)
			assert.NotNil(pkt)
		}

		// The next pop will fail because of gap
		pkt, err := jb.Pop()
		assert.ErrorIs(err, ErrNotFound)
		assert.Nil(pkt)
		assert.Equal(jb.PlayoutHead(), uint16(4))

		// Assert that PlayoutHead isn't modified with pushing/popping again
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 10, Timestamp: uint32(522)}, Payload: []byte{0x00}})
		pkt, err = jb.Pop()
		assert.ErrorIs(err, ErrNotFound)
		assert.Nil(pkt)
		assert.Equal(jb.PlayoutHead(), uint16(4))

		// Increment the PlayoutHead and popping will work again
		jb.SetPlayoutHead(jb.PlayoutHead() + 1)
		for i := 0; i < 6; i++ {
			pkt, err := jb.Pop()
			assert.NoError(err)
			assert.NotNil(pkt)
		}
	})

	t.Run("Allows clearing the buffer", func(*testing.T) {
		jb := New()
		jb.Clear(false)

		assert.Equal(jb.lastSequence, uint16(0))
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5000, Timestamp: 500}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5001, Timestamp: 501}, Payload: []byte{0x02}})
		jb.Push(&rtp.Packet{Header: rtp.Header{SequenceNumber: 5002, Timestamp: 502}, Payload: []byte{0x02}})

		assert.Equal(jb.lastSequence, uint16(5002))
		jb.Clear(true)
		assert.Equal(jb.lastSequence, uint16(0))
		assert.Equal(jb.stats.outOfOrderCount, uint32(0))
		assert.Equal(jb.packets.Length(), uint16(0))
	})
}
