package audio

import (
	"bytes"
	"encoding/binary"
	"io"
	"math"
	"sync"
	"sync/atomic"
	"testing"
)

func TestStreamSource_WriteRead(t *testing.T) {
	s := NewStreamSource(1024, 44100)
	if s.size != 1024 {
		t.Fatalf("expected size 1024, got %d", s.size)
	}

	// Write and read a frame
	f := NewFrame(0.5, -0.3)
	if !s.WriteFrame(f) {
		t.Fatal("write failed")
	}
	if s.Available() != 1 {
		t.Fatalf("expected 1 available, got %d", s.Available())
	}

	out := s.ReadFrame()
	if out.L != 0.5 || out.R != -0.3 {
		t.Fatalf("read mismatch: got L=%.2f R=%.2f", out.L, out.R)
	}
	if s.Available() != 0 {
		t.Fatalf("expected 0 available, got %d", s.Available())
	}
}

func TestStreamSource_Underrun(t *testing.T) {
	s := NewStreamSource(16, 44100)

	// Read from empty buffer — should return silence
	f := s.ReadFrame()
	if f.L != 0 || f.R != 0 {
		t.Fatal("expected silence on underrun")
	}
	if s.Underruns.Load() != 1 {
		t.Fatalf("expected 1 underrun, got %d", s.Underruns.Load())
	}
	stats := s.Stats()
	if stats.UnderrunStreak != 1 || stats.MaxUnderrunStreak != 1 {
		t.Fatalf("unexpected streak: %d/%d", stats.UnderrunStreak, stats.MaxUnderrunStreak)
	}
}

func TestStreamSource_UnderrunStreakTracking(t *testing.T) {
	s := NewStreamSource(16, 44100)
	for i := 0; i < 3; i++ {
		s.ReadFrame()
	}
	stats := s.Stats()
	if stats.UnderrunStreak != 3 {
		t.Fatalf("expected streak 3, got %d", stats.UnderrunStreak)
	}
	if stats.MaxUnderrunStreak != 3 {
		t.Fatalf("expected max streak 3, got %d", stats.MaxUnderrunStreak)
	}

	if !s.WriteFrame(NewFrame(0, 0)) {
		t.Fatal("expected write to succeed")
	}
	s.ReadFrame()
	stats = s.Stats()
	if stats.UnderrunStreak != 0 {
		t.Fatalf("expected streak reset to 0, got %d", stats.UnderrunStreak)
	}
	if stats.MaxUnderrunStreak != 3 {
		t.Fatalf("expected max streak to stay 3, got %d", stats.MaxUnderrunStreak)
	}
}

func TestStreamSource_Overflow(t *testing.T) {
	s := NewStreamSource(4, 44100) // size rounds up to 4

	// Fill completely
	for i := 0; i < 4; i++ {
		if !s.WriteFrame(NewFrame(Sample(float64(i)/10), 0)) {
			t.Fatalf("write %d failed", i)
		}
	}

	// Next write should overflow
	if s.WriteFrame(NewFrame(1, 1)) {
		t.Fatal("expected overflow")
	}
	if s.Overflows.Load() != 1 {
		t.Fatalf("expected 1 overflow, got %d", s.Overflows.Load())
	}
}

func TestStreamSource_PowerOf2Rounding(t *testing.T) {
	tests := []struct{ in, expect int }{
		{1, 1}, {2, 2}, {3, 4}, {5, 8}, {100, 128}, {1024, 1024}, {1025, 2048},
	}
	for _, tt := range tests {
		s := NewStreamSource(tt.in, 44100)
		if s.size != tt.expect {
			t.Fatalf("NewStreamSource(%d): size=%d, expected %d", tt.in, s.size, tt.expect)
		}
	}
}

func TestStreamSource_FIFO(t *testing.T) {
	s := NewStreamSource(64, 44100)
	n := 50
	for i := 0; i < n; i++ {
		s.WriteFrame(NewFrame(Sample(float64(i)), 0))
	}
	for i := 0; i < n; i++ {
		f := s.ReadFrame()
		if int(f.L) != i {
			t.Fatalf("FIFO order broken at %d: got %d", i, int(f.L))
		}
	}
}

func TestStreamSource_Wraparound(t *testing.T) {
	s := NewStreamSource(8, 44100) // size = 8

	// Write and read more than buffer size to test wraparound
	for round := 0; round < 10; round++ {
		for i := 0; i < 8; i++ {
			val := float64(round*8 + i)
			if !s.WriteFrame(NewFrame(Sample(val), 0)) {
				t.Fatalf("write failed round=%d i=%d", round, i)
			}
		}
		for i := 0; i < 8; i++ {
			expected := float64(round*8 + i)
			f := s.ReadFrame()
			if float64(f.L) != expected {
				t.Fatalf("round=%d i=%d: got %f expected %f", round, i, float64(f.L), expected)
			}
		}
	}

	stats := s.Stats()
	if stats.Underruns != 0 || stats.Overflows != 0 {
		t.Fatalf("unexpected errors: underruns=%d overflows=%d", stats.Underruns, stats.Overflows)
	}
}

func TestStreamSource_WritePCM(t *testing.T) {
	s := NewStreamSource(256, 44100)

	// Create 10 stereo frames of S16LE PCM
	var buf bytes.Buffer
	for i := 0; i < 10; i++ {
		l := int16(i * 1000)
		r := int16(-i * 1000)
		binary.Write(&buf, binary.LittleEndian, l)
		binary.Write(&buf, binary.LittleEndian, r)
	}

	written := s.WritePCM(buf.Bytes())
	if written != 10 {
		t.Fatalf("expected 10 frames, wrote %d", written)
	}

	// Verify first frame
	f := s.ReadFrame()
	if f.L != 0 || f.R != 0 {
		t.Fatalf("frame 0: L=%.4f R=%.4f, expected 0", f.L, f.R)
	}
	// Verify frame 5
	for i := 1; i < 5; i++ {
		s.ReadFrame()
	}
	f = s.ReadFrame()
	expectedL := 5000.0 / 32768.0
	if math.Abs(float64(f.L)-expectedL) > 0.001 {
		t.Fatalf("frame 5 L=%.4f, expected %.4f", f.L, expectedL)
	}
}

func TestStreamSource_ConcurrentSPSC(t *testing.T) {
	s := NewStreamSource(4096, 44100)
	frames := 50000
	var producerDone atomic.Bool

	var wg sync.WaitGroup
	wg.Add(2)

	// Producer
	go func() {
		defer wg.Done()
		for i := 0; i < frames; i++ {
			for !s.WriteFrame(NewFrame(Sample(float64(i+1)), 0)) {
				// Buffer full — yield
			}
		}
		producerDone.Store(true)
	}()

	// Consumer
	var lastVal float64
	var orderOK = true
	var readCount int
	go func() {
		defer wg.Done()
		for {
			if s.Available() == 0 {
				if producerDone.Load() {
					break
				}
				continue
			}
			f := s.ReadFrame()
			readCount++
			v := float64(f.L)
			if v > 0 && v < lastVal {
				orderOK = false
			}
			if v > 0 {
				lastVal = v
			}
		}
	}()

	wg.Wait()

	if !orderOK {
		t.Fatal("FIFO order broken in concurrent SPSC")
	}
	if readCount < frames/2 {
		t.Fatalf("read too few frames: %d (expected ~%d)", readCount, frames)
	}
}

func TestStreamSource_StatsBufferedDuration(t *testing.T) {
	rate := 48000
	s := NewStreamSource(128, rate)
	for i := 0; i < 24; i++ {
		s.WriteFrame(NewFrame(0, 0))
	}
	stats := s.Stats()
	if stats.BufferedDurationSeconds <= 0 {
		t.Fatalf("expected buffered duration > 0, got %.6f", stats.BufferedDurationSeconds)
	}
	expected := float64(stats.Available) / float64(rate)
	if math.Abs(stats.BufferedDurationSeconds-expected) > 1e-9 {
		t.Fatalf("buffered duration %.9f != expected %.9f", stats.BufferedDurationSeconds, expected)
	}
}

func TestStreamSource_StatsHighWatermark(t *testing.T) {
	rate := 44100
	s := NewStreamSource(64, rate)
	for i := 0; i < 12; i++ {
		s.WriteFrame(NewFrame(0, 0))
	}
	for i := 0; i < 5; i++ {
		s.ReadFrame()
	}
	stats := s.Stats()
	if stats.HighWatermark != 12 {
		t.Fatalf("expected high watermark 12, got %d", stats.HighWatermark)
	}
	expected := float64(stats.HighWatermark) / float64(rate)
	if math.Abs(stats.HighWatermarkDurationSeconds-expected) > 1e-9 {
		t.Fatalf("high watermark duration %.9f != %.9f", stats.HighWatermarkDurationSeconds, expected)
	}
	if stats.HighWatermark < stats.Available {
		t.Fatalf("high watermark %d < available %d", stats.HighWatermark, stats.Available)
	}
}


// --- StreamResampler tests ---

func TestStreamResampler_1to1(t *testing.T) {
	s := NewStreamSource(256, 44100)
	r := NewStreamResampler(s, 44100) // 1:1

	for i := 0; i < 100; i++ {
		s.WriteFrame(NewFrame(Sample(float64(i)/100), 0))
	}

	// At 1:1 ratio, output should track input with a small startup delay.
	// Skip first few samples (resampler priming), then verify monotonic increase.
	prev := -1.0
	for i := 0; i < 90; i++ {
		f := r.NextFrame()
		v := float64(f.L)
		if i > 5 && v < prev-0.001 {
			t.Fatalf("sample %d: non-monotonic %.4f < %.4f", i, v, prev)
		}
		if v > 0 {
			prev = v
		}
	}
	// Final value should be close to 0.9 (we wrote 0..0.99)
	if prev < 0.5 {
		t.Fatalf("final value %.4f too low (expected > 0.5)", prev)
	}
}

func TestStreamResampler_Upsample(t *testing.T) {
	// 44100 → 228000 (ratio ≈ 0.1934, ~5.17× upsampling)
	s := NewStreamSource(4096, 44100)
	r := NewStreamResampler(s, 228000)

	// Write 1000 frames of a 1kHz sine at 44100 Hz
	for i := 0; i < 1000; i++ {
		v := math.Sin(2 * math.Pi * 1000 * float64(i) / 44100)
		s.WriteFrame(NewFrame(Sample(v), Sample(v)))
	}

	// Read upsampled output — should be ~5170 samples for 1000 input
	// (minus a few for resampler priming)
	out := make([]float64, 0, 5200)
	for i := 0; i < 5000; i++ {
		f := r.NextFrame()
		out = append(out, float64(f.L))
	}

	// Verify the output is a smooth sine, not clicks or zeros
	// Check that max amplitude is close to 1.0
	maxAmp := 0.0
	for _, v := range out[100:] { // skip initial ramp
		if math.Abs(v) > maxAmp {
			maxAmp = math.Abs(v)
		}
	}
	if maxAmp < 0.8 {
		t.Fatalf("max amplitude %.4f too low (expected ~1.0)", maxAmp)
	}

	// Check smoothness: no sudden jumps > 0.1 between adjacent samples
	maxJump := 0.0
	for i := 101; i < len(out); i++ {
		d := math.Abs(out[i] - out[i-1])
		if d > maxJump {
			maxJump = d
		}
	}
	// At 228kHz with 1kHz tone: max step ≈ sin(2π*1000/228000) ≈ 0.0276
	if maxJump > 0.05 {
		t.Fatalf("max inter-sample jump %.4f (expected < 0.05 for smooth sine)", maxJump)
	}
}

func TestStreamResampler_Downsample(t *testing.T) {
	// 96000 → 44100 (ratio ≈ 2.177, downsampling)
	s := NewStreamSource(8192, 96000)
	r := NewStreamResampler(s, 44100)

	// Write 4000 frames at 96kHz
	for i := 0; i < 4000; i++ {
		v := math.Sin(2 * math.Pi * 440 * float64(i) / 96000)
		s.WriteFrame(NewFrame(Sample(v), 0))
	}

	// Should get ~1837 output frames (4000 × 44100/96000)
	count := 0
	for i := 0; i < 1800; i++ {
		f := r.NextFrame()
		_ = f
		count++
	}
	if count != 1800 {
		t.Fatalf("expected 1800 reads, got %d", count)
	}
}

func TestStreamResampler_NilSource(t *testing.T) {
	r := NewStreamResampler(nil, 228000)
	f := r.NextFrame()
	if f.L != 0 || f.R != 0 {
		t.Fatal("expected silence from nil source")
	}
}

// --- IngestReader test ---

func TestIngestReader(t *testing.T) {
	s := NewStreamSource(4096, 44100)

	// Create PCM data: 100 stereo frames
	var buf bytes.Buffer
	for i := 0; i < 100; i++ {
		l := int16(i * 100)
		r := int16(-i * 100)
		binary.Write(&buf, binary.LittleEndian, l)
		binary.Write(&buf, binary.LittleEndian, r)
	}

	// IngestReader should read all data and return nil (EOF)
	err := IngestReader(bytes.NewReader(buf.Bytes()), s)
	if err != nil {
		t.Fatalf("IngestReader: %v", err)
	}

	if s.Available() != 100 {
		t.Fatalf("expected 100 frames, got %d", s.Available())
	}

	// Verify first and last
	f := s.ReadFrame()
	if f.L != 0 {
		t.Fatalf("frame 0 L=%.4f, expected 0", f.L)
	}
	for i := 1; i < 99; i++ {
		s.ReadFrame()
	}
	f = s.ReadFrame()
	expectedL := 9900.0 / 32768.0
	if math.Abs(float64(f.L)-expectedL) > 0.01 {
		t.Fatalf("frame 99 L=%.4f, expected ~%.4f", f.L, expectedL)
	}
}

func TestIngestReader_Error(t *testing.T) {
	s := NewStreamSource(256, 44100)
	errReader := &errAfterN{n: 10}
	err := IngestReader(errReader, s)
	if err == nil {
		t.Fatal("expected error")
	}
}

type errAfterN struct {
	n, count int
}

func (r *errAfterN) Read(p []byte) (int, error) {
	if r.count >= r.n {
		return 0, io.ErrUnexpectedEOF
	}
	r.count++
	// Return 4 bytes (one stereo frame)
	if len(p) >= 4 {
		p[0], p[1], p[2], p[3] = 0, 0, 0, 0
		return 4, nil
	}
	return 0, nil
}
