package main

import (
	"math"
	"testing"
	"time"

	"github.com/kami/maven/internal/audio"
)

// frameRMSQuick computes RMS of raw PCM int16 LE bytes.
func frameRMSQuick(raw []byte) float64 {
	if len(raw) == 0 {
		return 0
	}
	var sum float64
	for i := 0; i < len(raw); i += 2 {
		s := int16(raw[i]) | int16(raw[i+1])<<8
		f := float64(s) / 32768.0
		sum += f * f
	}
	return math.Sqrt(sum / float64(len(raw)/2))
}

// loudFrame returns a 30ms frame of loud sine tone (RMS ~0.5).
func loudFrame() []int16 {
	f := make([]int16, frameSamples)
	for i := range f {
		f[i] = int16(16000 * math.Sin(2*math.Pi*440*float64(i)/16000))
	}
	return f
}

// quietFrame returns a 30ms frame of near-silence (RMS ~0.005).
func quietFrame() []int16 {
	f := make([]int16, frameSamples)
	for i := range f {
		f[i] = int16(80 * math.Sin(2*math.Pi*440*float64(i)/16000))
	}
	return f
}

// silentFrame returns a true silent frame (all zeros).
func silentFrame() []int16 {
	return make([]int16, frameSamples)
}

func TestVAD_InitialState(t *testing.T) {
	v := NewVAD(0, 0, 0, 0)
	if v.State() != StateSilence {
		t.Fatalf("initial state = %d, want %d (StateSilence)", v.State(), StateSilence)
	}
	if v.FrameSamples() != frameSamples {
		t.Fatalf("FrameSamples = %d, want %d", v.FrameSamples(), frameSamples)
	}
}

func TestVAD_SilenceStaysSilence(t *testing.T) {
	v := NewVAD(0, 0, 0, 0)
	// Feed silence for many frames — should never transition to speech.
	for i := 0; i < 50; i++ {
		utt, state := v.Feed(silentFrame())
		if utt.Bytes != nil {
			t.Fatalf("unexpected utterance at frame %d", i)
		}
		if state != StateSilence {
			t.Fatalf("state = %d at frame %d, want StateSilence", state, i)
		}
	}
}

func TestVAD_SpeechTransition(t *testing.T) {
	v := NewVAD(0, 0, 0, 0)
	// Frames needed to trigger speech: ceil(300ms / 30ms) = 10
	speechFrames := (defaultSpeechMs + defaultFrameMs - 1) / defaultFrameMs

	// Feed loud frames one by one. On the last frame state flips to Speech;
	// no utterance returned — still accumulating.
	for i := 0; i < speechFrames; i++ {
		utt, state := v.Feed(loudFrame())
		if i < speechFrames-1 {
			if state != StateSilence {
				t.Fatalf("state = %d at frame %d, want StateSilence", state, i)
			}
		} else {
			if state != StateSpeech {
				t.Fatalf("state after trigger = %d, want StateSpeech", state)
			}
		}
		if utt.Bytes != nil {
			t.Fatalf("unexpected utterance at frame %d (utterance not done)", i)
		}
	}
}

func TestVAD_UtteranceComplete(t *testing.T) {
	v := NewVAD(0, 0, 0, 0)
	speechFrames := (defaultSpeechMs + defaultFrameMs - 1) / defaultFrameMs
	// Silence needs ceil(800/30) = 27 frames; use the tracked duration check.
	silenceFrames := (defaultSilenceMs + defaultFrameMs - 1) / defaultFrameMs

	// Trigger speech.
	for i := 0; i < speechFrames; i++ {
		v.Feed(loudFrame())
	}

	// Feed a few speech frames.
	for i := 0; i < 5; i++ {
		utt, state := v.Feed(loudFrame())
		if utt.Bytes != nil {
			t.Fatalf("unexpected utterance during speech at frame %d", i)
		}
		if state != StateSpeech {
			t.Fatalf("state = %d during speech, want StateSpeech", state)
		}
	}

	// Feed silence frames until utterance completes.
	for i := 0; i < silenceFrames; i++ {
		utt, state := v.Feed(quietFrame())
		if i < silenceFrames-1 {
			if utt.Bytes != nil {
				t.Fatalf("unexpected utterance before silence triggers at frame %d", i)
			}
			if state != StateSpeech {
				t.Fatalf("state = %d during silence tail, want StateSpeech", state)
			}
		} else {
			// Last frame should trigger utterance complete.
			if state != StateSilence {
				t.Fatalf("state after utterance = %d, want StateSilence", state)
			}
			if utt.Bytes == nil {
				t.Fatal("expected non-nil utterance after silence trigger")
			}
			dur := audio.Audio{Format: audio.PCM16kMono, Bytes: utt.Bytes}.Duration()
			if dur <= 0 {
				t.Fatalf("utterance duration %.2f, want > 0", dur)
			}
			t.Logf("utterance: %.2fs, %d bytes", dur, len(utt.Bytes))
		}
	}
}

func TestVAD_MaxUtteranceLength(t *testing.T) {
	v := NewVAD(0, 0, 0, 100) // max 100ms
	speechFrames := defaultSpeechMs / defaultFrameMs

	// Trigger speech.
	for i := 0; i < speechFrames; i++ {
		v.Feed(loudFrame())
	}

	// Calculate how many frames until max (100ms / 30ms = 3.33 → ceil 4)
	maxFrames := 100 / defaultFrameMs // 3
	if 100%defaultFrameMs != 0 {
		maxFrames++
	}

	for i := 0; i < maxFrames-1; i++ {
		utt, state := v.Feed(loudFrame())
		if utt.Bytes != nil {
			t.Fatalf("unexpected utterance at frame %d before max", i)
		}
		if state != StateSpeech {
			t.Fatalf("unexpected state %d during speech", state)
		}
	}

	// This frame should hit the max and force-end.
	utt, state := v.Feed(loudFrame())
	if state != StateSilence {
		t.Fatalf("state after max = %d, want StateSilence", state)
	}
	if utt.Bytes == nil {
		t.Fatal("expected utterance after max duration")
	}
	dur := audio.Audio{Format: audio.PCM16kMono, Bytes: utt.Bytes}.Duration()
	if dur > 150*time.Millisecond.Seconds() {
		t.Fatalf("utterance too long: %.2fs, want <= 150ms", dur)
	}
}

func TestVAD_Reset(t *testing.T) {
	v := NewVAD(0, 0, 0, 0)
	speechFrames := defaultSpeechMs / defaultFrameMs

	// Start speaking
	for i := 0; i < speechFrames; i++ {
		v.Feed(loudFrame())
	}
	if v.State() != StateSpeech {
		t.Fatalf("expected StateSpeech after trigger")
	}

	// Reset
	v.Reset()
	if v.State() != StateSilence {
		t.Fatalf("expected StateSilence after reset")
	}

	// Should be back to silence
	utt, state := v.Feed(quietFrame())
	if utt.Bytes != nil {
		t.Fatal("expected nil utterance after reset + silence")
	}
	if state != StateSilence {
		t.Fatalf("expected StateSilence after reset, got %d", state)
	}
}

func TestVAD_ShortSpeechNotTriggered(t *testing.T) {
	v := NewVAD(0, 0, 0, 0)
	// A few loud frames below the speech_ms threshold should not trigger.
	for i := 0; i < 3; i++ {
		utt, state := v.Feed(loudFrame())
		if utt.Bytes != nil {
			t.Fatalf("unexpected utterance at frame %d", i)
		}
		if state != StateSilence {
			t.Fatalf("expected StateSilence for short speech, got %d", state)
		}
	}
}

func TestVAD_AdaptiveFloor(t *testing.T) {
	v := NewVAD(100, 0, 0, 0) // minRMS = 100/10000 = 0.01
	speechFrames := defaultSpeechMs / defaultFrameMs

	// Very quiet frames should lower the floor.
	for i := 0; i < 30; i++ {
		v.Feed(silentFrame())
	}

	// floorRMS should be ~0.01 (minRMS clamp)
	if v.floorRMS < 0.009 || v.floorRMS > 0.011 {
		t.Fatalf("floorRMS after silence = %.4f, want ~0.01", v.floorRMS)
	}

	// A frame at RMS ~0.005 (quietFrame) should now be below the floor
	// and stay silence.
	for i := 0; i < speechFrames; i++ {
		_, state := v.Feed(quietFrame())
		if state != StateSilence {
			t.Fatalf("quiet frame triggered speech (floor=%.4f)", v.floorRMS)
		}
	}
}

func TestVAD_EmptyFrame(t *testing.T) {
	v := NewVAD(0, 0, 0, 0)
	utt, state := v.Feed(nil)
	if utt.Bytes != nil {
		t.Fatal("expected nil utterance for empty frame")
	}
	_ = state // state is undefined for empty; just don't panic
}

func TestVAD_ConstructedUtterance(t *testing.T) {
	// Test a realistic scenario: speech followed by silence.
	v := NewVAD(0, 0, 0, 0)
	speechFrames := (defaultSpeechMs + defaultFrameMs - 1) / defaultFrameMs
	silenceFrames := (defaultSilenceMs + defaultFrameMs - 1) / defaultFrameMs

	// Build frames: trigger threshold + 20 speech frames + silence tail.
	totalSpeech := speechFrames + 20
	frames := make([][]int16, 0, totalSpeech+silenceFrames)
	for i := 0; i < totalSpeech; i++ {
		frames = append(frames, loudFrame())
	}
	for i := 0; i < silenceFrames; i++ {
		frames = append(frames, silentFrame())
	}

	var gotUtterance bool
	var uttCount int
	for _, f := range frames {
		utt, state := v.Feed(f)
		if utt.Bytes != nil {
			gotUtterance = true
			uttCount++
			dur := audio.Audio{Format: audio.PCM16kMono, Bytes: utt.Bytes}.Duration()

			// Utterance includes: 1 trigger frame + 20 speech + all silence
			// frames before the threshold frames. silenceFrames count already
			// includes the trailing frame that trips the return.
			expectedFrames := 1 + 20 + silenceFrames
			expectedDur := float64(expectedFrames) * float64(defaultFrameMs) / 1000.0
			if dur < expectedDur*0.8 || dur > expectedDur*1.2 {
				t.Fatalf("utterance duration %.2fs (expected ~%.2fs, %d frames, silenceFrames=%d)",
					dur, expectedDur, expectedFrames, silenceFrames)
			}
			t.Logf("utterance: %.2fs, expected ~%.2fs (%d frames)", dur, expectedDur, expectedFrames)
		}
		if state != StateSilence && utt.Bytes != nil {
			t.Fatalf("utterance returned but state = %d, want StateSilence", state)
		}
	}
	if !gotUtterance {
		t.Fatal("no utterance produced from speech+silence")
	}
	if uttCount != 1 {
		t.Fatalf("expected exactly 1 utterance, got %d", uttCount)
	}
	if v.State() != StateSilence {
		t.Fatalf("final state = %d, want StateSilence", v.State())
	}
}

// Benchmarks.
func BenchmarkVAD(b *testing.B) {
	v := NewVAD(0, 0, 0, 0)
	f := loudFrame()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		v.Feed(f)
	}
}

func BenchmarkFrameRMS(b *testing.B) {
	f := loudFrame()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		frameRMS(f)
	}
}
