package teamspeak

import (
	"encoding/binary"
	"fmt"
	"slices"
	"sync"
	"testing"
	"time"
)

type fakeDecoder struct {
	frames   [][]int16
	lost     int
	released bool
}

func (d *fakeDecoder) Decode([]byte) ([]int16, error) {
	f := d.frames[0]
	d.frames = d.frames[1:]
	return f, nil
}
func (d *fakeDecoder) DecodeLost() ([]int16, error) {
	d.lost++
	return make([]int16, receiveFrameInterleavedSamples), nil
}
func (d *fakeDecoder) Release() { d.released = true }

type fakeFactory struct{ decoder *fakeDecoder }

func (f *fakeFactory) NewvoiceDecoder(int, int) (voiceDecoder, error) { return f.decoder, nil }
func frame(value int16) []int16 {
	return stereoFrame(value, value)
}

func stereoFrame(left, right int16) []int16 {
	f := make([]int16, receiveFrameInterleavedSamples)
	for i := 0; i < len(f); i += receiveChannels {
		f[i], f[i+1] = left, right
	}
	return f
}

func TestVoiceCodecChannelsAndMonoNormalization(t *testing.T) {
	if got := voiceCodecChannels(4); got != 1 {
		t.Fatalf("codec 4 channels=%d", got)
	}
	if got := voiceCodecChannels(5); got != receiveChannels {
		t.Fatalf("codec 5 channels=%d", got)
	}

	got := monoToStereo([]int16{10, -20, 30})
	want := []int16{10, 10, -20, -20, 30, 30}
	if !slices.Equal(got, want) {
		t.Fatalf("mono normalization=%v, want %v", got, want)
	}
}

func TestStereoNormalizationPreservesChannels(t *testing.T) {
	pcm := []int16{10, -10, 20, -20}
	got := normalizeDecodedPCM(pcm, receiveChannels)
	if !slices.Equal(got, pcm) {
		t.Fatalf("stereo normalization=%v, want %v", got, pcm)
	}
}

func TestPLCUsesStereoFrameContract(t *testing.T) {
	d := &fakeDecoder{}
	pcm, err := d.DecodeLost()
	if err != nil {
		t.Fatal(err)
	}
	if len(pcm) != receiveFrameInterleavedSamples {
		t.Fatalf("PLC samples=%d", len(pcm))
	}
}

func TestTimelinePrebufferPLCAndSequenceWrap(t *testing.T) {
	d := &fakeDecoder{frames: [][]int16{frame(1), frame(2), frame(3), frame(4)}}
	tl := newReceiveTimeline(1, d)
	at := time.Now()
	for _, seq := range []uint16{65534, 65535, 0} {
		tl.push(voicePacket{sequence: seq, data: []byte{1}, at: at})
	}
	for _, want := range []int16{1, 2, 3} {
		got, active := tl.next()
		if !active || got[0] != want {
			t.Fatalf("got active=%v sample=%d, want %d", active, got[0], want)
		}
	}
	tl.push(voicePacket{sequence: 2, data: []byte{1}, at: at})
	if _, active := tl.next(); active || d.lost != 0 {
		t.Fatalf("expected jitter wait before PLC")
	}
	if _, active := tl.next(); active || d.lost != 0 {
		t.Fatalf("expected second jitter wait before PLC")
	}
	if _, active := tl.next(); !active || d.lost != 1 {
		t.Fatalf("expected PLC after confirmed gap")
	}
	tl.packets[2] = voicePacket{sequence: 2, data: []byte{1}, at: at}
	got, active := tl.next()
	if !active || got[0] != 4 {
		t.Fatalf("got active=%v sample=%d after PLC", active, got[0])
	}
}

func TestTimelineSplitsSupportedOpusDurationsIntoTwentyMillisecondFrames(t *testing.T) {
	for _, durationMS := range []int{10, 20, 40, 60} {
		t.Run(fmt.Sprintf("%dms", durationMS), func(t *testing.T) {
			samples := receiveSampleRate * durationMS / 1000 * receiveChannels
			pcm := make([]int16, samples)
			for i := range pcm {
				pcm[i] = int16(durationMS)
			}

			tl := newReceiveTimeline(1, &fakeDecoder{})
			frames := 0
			if frame, ok := tl.takeFrame(pcm); ok {
				frames++
				if len(frame) != receiveFrameInterleavedSamples {
					t.Fatalf("first frame has %d samples", len(frame))
				}
			}
			for {
				frame, ok := tl.popPending()
				if !ok {
					break
				}
				frames++
				if len(frame) != receiveFrameInterleavedSamples {
					t.Fatalf("pending frame has %d samples", len(frame))
				}
			}

			wantFrames := durationMS / 20
			if frames != wantFrames {
				t.Fatalf("got %d complete frames, want %d (pending=%d)", frames, wantFrames, len(tl.pending))
			}
			wantPending := 0
			if durationMS == 10 {
				wantPending = receiveFrameInterleavedSamples / 2
			}
			if len(tl.pending) != wantPending {
				t.Fatalf("pending=%d, want %d", len(tl.pending), wantPending)
			}
		})
	}
}

func TestTimelineAccumulatesTwoTenMillisecondFrames(t *testing.T) {
	half := make([]int16, receiveFrameInterleavedSamples/2)
	for i := range half {
		half[i] = 7
	}
	d := &fakeDecoder{frames: [][]int16{half, half, frame(9)}}
	tl := newReceiveTimeline(1, d)
	at := time.Now()
	for sequence := uint16(1); sequence <= 3; sequence++ {
		tl.push(voicePacket{sequence: sequence, data: []byte{1}, at: at})
	}
	if _, active := tl.next(); active {
		t.Fatal("10 ms frame should remain pending")
	}
	got, active := tl.next()
	if !active || len(got) != receiveFrameInterleavedSamples || got[0] != 7 || got[len(got)-1] != 7 {
		t.Fatalf("10 ms frames were not combined: active=%v len=%d", active, len(got))
	}
	got, active = tl.next()
	if !active || got[0] != 9 {
		t.Fatalf("next decoded frame was not preserved: active=%v", active)
	}
}

func TestTimelinePrebufferSurvivesTicksBetweenPackets(t *testing.T) {
	d := &fakeDecoder{frames: [][]int16{frame(1)}}
	tl := newReceiveTimeline(1, d)
	at := time.Now()
	for sequence := uint16(1); sequence <= 3; sequence++ {
		tl.push(voicePacket{sequence: sequence, data: []byte{1}, at: at.Add(time.Duration(sequence) * 20 * time.Millisecond)})
		if sequence < 3 {
			if _, active := tl.next(); active {
				t.Fatal("timeline played before prebuffer was ready")
			}
		}
	}
	if !tl.ready || len(tl.packets) != receivePrebuffer {
		t.Fatalf("interleaved ticks reset prebuffer: ready=%v packets=%d expected=%d", tl.ready, len(tl.packets), tl.expected)
	}
	if pcm, active := tl.next(); !active || pcm[0] != 1 {
		t.Fatal("timeline did not play after interleaved prebuffer")
	}
}

func TestTimelineRebasesAfterTalkSpurtGap(t *testing.T) {
	d := &fakeDecoder{frames: [][]int16{frame(1), frame(2), frame(3), frame(4)}}
	tl := newReceiveTimeline(1, d)
	start := time.Now()
	for sequence := uint16(100); sequence < 103; sequence++ {
		tl.push(voicePacket{sequence: sequence, data: []byte{1}, at: start})
	}
	for range 3 {
		tl.next()
	}
	resume := start.Add(receiveTalkSpurtGap + time.Millisecond)
	for sequence := uint16(200); sequence < 203; sequence++ {
		tl.push(voicePacket{sequence: sequence, data: []byte{1}, at: resume})
	}
	if !tl.ready || tl.expected != 200 {
		t.Fatalf("talk spurt did not rebase: ready=%v expected=%d", tl.ready, tl.expected)
	}
}

func TestTimelineSilenceDoesNotAdvanceExpected(t *testing.T) {
	tl := newReceiveTimeline(1, &fakeDecoder{frames: [][]int16{frame(1), frame(2), frame(3)}})
	at := time.Now()
	for sequence := uint16(10); sequence < 13; sequence++ {
		tl.push(voicePacket{sequence: sequence, data: []byte{1}, at: at})
	}
	for range 3 {
		tl.next()
	}
	expected := tl.expected
	for range 10 {
		tl.next()
	}
	if tl.expected != expected {
		t.Fatalf("silence advanced expected from %d to %d", expected, tl.expected)
	}
}

func TestWorkerEmitsSilenceForActiveTimeline(t *testing.T) {
	d := &fakeDecoder{frames: [][]int16{frame(1), frame(1), frame(1)}}
	var frames [][]byte
	r := newReceiveAudio(&fakeFactory{decoder: d}, func(p []byte) { frames = append(frames, p) }, nil)
	timelines := map[int]*receiveTimeline{1: newReceiveTimeline(1, d)}
	at := time.Now()
	for sequence := uint16(1); sequence <= 3; sequence++ {
		timelines[1].push(voicePacket{sequence: sequence, data: []byte{1}, at: at})
	}
	r.tick(timelines, at)
	r.tick(timelines, at.Add(20*time.Millisecond))
	if len(frames) != 2 || len(frames[0]) != receiveFrameBytes || len(frames[1]) != receiveFrameBytes {
		t.Fatalf("expected fixed silent-capable output, got %d frames", len(frames))
	}
}

func TestTimelineDuplicateOldAndBounded(t *testing.T) {
	tl := newReceiveTimeline(1, &fakeDecoder{})
	at := time.Now()
	tl.push(voicePacket{sequence: 10, data: []byte{1}, at: at})
	tl.push(voicePacket{sequence: 10, data: []byte{2}, at: at})
	tl.push(voicePacket{sequence: 9, data: []byte{3}, at: at})
	if len(tl.packets) != 1 {
		t.Fatalf("accepted duplicate or old packets: %d", len(tl.packets))
	}
	for i := uint16(11); i < 40; i++ {
		tl.push(voicePacket{sequence: i, data: []byte{1}, at: at})
	}
	if len(tl.packets) > receiveMaxPackets {
		t.Fatalf("packet map exceeds cap: %d", len(tl.packets))
	}
}

func TestMixerClampsAndPCMFormat(t *testing.T) {
	acc := make([]int32, receiveFrameInterleavedSamples)
	acc[0], acc[1] = 40000, -40000
	out := mixPCM16(acc)
	if len(out) != receiveFrameBytes {
		t.Fatalf("got %d bytes", len(out))
	}
	if got := int16(binary.LittleEndian.Uint16(out)); got != 32767 {
		t.Fatalf("positive clamp=%d", got)
	}
	if got := int16(binary.LittleEndian.Uint16(out[2:])); got != -32768 {
		t.Fatalf("negative clamp=%d", got)
	}
}

func TestMixerPreservesIndependentStereoChannels(t *testing.T) {
	acc := make([]int32, receiveFrameInterleavedSamples)
	for i := 0; i < len(acc); i += receiveChannels {
		acc[i], acc[i+1] = 100, -200
	}
	out := mixPCM16(acc)
	if got := int16(binary.LittleEndian.Uint16(out)); got != 100 {
		t.Fatalf("left channel=%d", got)
	}
	if got := int16(binary.LittleEndian.Uint16(out[2:])); got != -200 {
		t.Fatalf("right channel=%d", got)
	}
}

func TestNewClientInstallsPlatformFactoryWhenAvailable(t *testing.T) {
	c := NewClient()
	if newPlatformVoiceDecoderFactory() != nil && c.receiveFactory == nil {
		t.Fatal("NewClient did not install the platform voice decoder factory")
	}
}

func TestReceiveWorkerRestartsAfterStop(t *testing.T) {
	c := &TSClient{receiveFactory: &fakeFactory{decoder: &fakeDecoder{}}}
	c.StartReceiveAudio()
	first := c.receive
	if first == nil {
		t.Fatal("StartReceiveAudio did not create a worker")
	}
	c.StopReceiveAudio()
	c.StartReceiveAudio()
	if c.receive == nil || c.receive == first {
		t.Fatal("StartReceiveAudio did not create a fresh worker after stop")
	}
	c.StopReceiveAudio()
}

func TestWorkerMuteDoesNotStopDecodeOrSpeaking(t *testing.T) {
	d := &fakeDecoder{frames: [][]int16{frame(100), frame(100), frame(100)}}
	var mu sync.Mutex
	var pcm [][]byte
	var speaking []bool
	r := newReceiveAudio(&fakeFactory{decoder: d}, func(p []byte) { mu.Lock(); pcm = append(pcm, p); mu.Unlock() }, func(_ int, v bool) { mu.Lock(); speaking = append(speaking, v); mu.Unlock() })
	r.start()
	for i := 1; i <= 3; i++ {
		r.enqueue(1, 4, uint16(i), []byte{1})
	}
	time.Sleep(30 * time.Millisecond)
	r.command(audioCommand{kind: "mute", clientID: 1, muted: true})
	time.Sleep(70 * time.Millisecond)
	r.stopAndWait()
	mu.Lock()
	defer mu.Unlock()
	if len(speaking) == 0 || !speaking[0] {
		t.Fatal("mute prevented speaking")
	}
	if len(d.frames) >= 3 {
		t.Fatal("mute prevented decoding")
	}
	if !d.released {
		t.Fatal("worker did not release decoder")
	}
	// At least the initial unmuted tick may mix; muted ticks must not require PCM.
	_ = pcm
}
