package audio

import (
	"encoding/binary"
	"os"
	"path/filepath"
	"testing"
)

func TestPCM16ToSample(t *testing.T) {
	if pcm16ToSample(32767) <= 0 {
		t.Fatal("expected positive sample")
	}
	if pcm16ToSample(-32768) < -1.0 {
		t.Fatal("expected clamped lower bound")
	}
}

func TestLoadWAVSource(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.wav")
	wav := buildMinimalWAV(48000, 1, []int16{0, 32767, -32768, 0})
	if err := os.WriteFile(path, wav, 0o644); err != nil {
		t.Fatalf("write wav: %v", err)
	}
	src, err := LoadWAVSource(path)
	if err != nil {
		t.Fatalf("LoadWAVSource failed: %v", err)
	}
	if src.SampleRate != 48000 {
		t.Fatalf("unexpected sample rate: %d", src.SampleRate)
	}
	if src.Channels != 1 {
		t.Fatalf("unexpected channels: %d", src.Channels)
	}
	_ = src.NextFrame()
}

func TestLoadWAVWithExtraChunks(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "extra.wav")

	// Build a WAV with a LIST chunk between fmt and data
	wav := buildWAVWithExtraChunks(44100, 1, []int16{100, -100})
	if err := os.WriteFile(path, wav, 0o644); err != nil {
		t.Fatalf("write wav: %v", err)
	}
	src, err := LoadWAVSource(path)
	if err != nil {
		t.Fatalf("LoadWAVSource with extra chunks failed: %v", err)
	}
	if src.SampleRate != 44100 {
		t.Fatalf("unexpected sample rate: %d", src.SampleRate)
	}
	if len(src.frames) != 2 {
		t.Fatalf("expected 2 frames, got %d", len(src.frames))
	}
}

func TestRejectInvalidWAV(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "bad.wav")
	if err := os.WriteFile(path, []byte("nope"), 0o644); err != nil {
		t.Fatalf("write wav: %v", err)
	}
	if _, err := LoadWAVSource(path); err == nil {
		t.Fatal("expected wav load error")
	}
}

func TestStereoWAV(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "stereo.wav")
	wav := buildMinimalWAV(48000, 2, []int16{1000, -1000, 2000, -2000})
	if err := os.WriteFile(path, wav, 0o644); err != nil {
		t.Fatalf("write wav: %v", err)
	}
	src, err := LoadWAVSource(path)
	if err != nil {
		t.Fatalf("LoadWAVSource stereo failed: %v", err)
	}
	if src.Channels != 2 {
		t.Fatalf("expected 2 channels, got %d", src.Channels)
	}
	if len(src.frames) != 2 {
		t.Fatalf("expected 2 frames, got %d", len(src.frames))
	}
}

// -- helpers --

func buildMinimalWAV(sampleRate int, channels int, samples []int16) []byte {
	dataSize := len(samples) * 2
	fileSize := 36 + dataSize

	buf := make([]byte, 0, 44+dataSize)
	buf = append(buf, []byte("RIFF")...)
	buf = binary.LittleEndian.AppendUint32(buf, uint32(fileSize))
	buf = append(buf, []byte("WAVE")...)

	// fmt chunk
	buf = append(buf, []byte("fmt ")...)
	buf = binary.LittleEndian.AppendUint32(buf, 16)
	buf = binary.LittleEndian.AppendUint16(buf, 1) // PCM
	buf = binary.LittleEndian.AppendUint16(buf, uint16(channels))
	buf = binary.LittleEndian.AppendUint32(buf, uint32(sampleRate))
	buf = binary.LittleEndian.AppendUint32(buf, uint32(sampleRate*channels*2)) // byte rate
	buf = binary.LittleEndian.AppendUint16(buf, uint16(channels*2))            // block align
	buf = binary.LittleEndian.AppendUint16(buf, 16)                            // bits per sample

	// data chunk
	buf = append(buf, []byte("data")...)
	buf = binary.LittleEndian.AppendUint32(buf, uint32(dataSize))
	for _, s := range samples {
		buf = binary.LittleEndian.AppendUint16(buf, uint16(s))
	}
	return buf
}

func buildWAVWithExtraChunks(sampleRate int, channels int, samples []int16) []byte {
	dataSize := len(samples) * 2
	// Add a fake LIST chunk of 12 bytes between fmt and data
	listChunkData := []byte("INFOtest")                     // 8 bytes
	listChunkSize := uint32(len(listChunkData))
	totalExtraChunk := 8 + len(listChunkData)               // "LIST" + size + data
	fileSize := 36 + totalExtraChunk + dataSize

	buf := make([]byte, 0, 44+totalExtraChunk+dataSize)
	buf = append(buf, []byte("RIFF")...)
	buf = binary.LittleEndian.AppendUint32(buf, uint32(fileSize))
	buf = append(buf, []byte("WAVE")...)

	// fmt chunk
	buf = append(buf, []byte("fmt ")...)
	buf = binary.LittleEndian.AppendUint32(buf, 16)
	buf = binary.LittleEndian.AppendUint16(buf, 1)
	buf = binary.LittleEndian.AppendUint16(buf, uint16(channels))
	buf = binary.LittleEndian.AppendUint32(buf, uint32(sampleRate))
	buf = binary.LittleEndian.AppendUint32(buf, uint32(sampleRate*channels*2))
	buf = binary.LittleEndian.AppendUint16(buf, uint16(channels*2))
	buf = binary.LittleEndian.AppendUint16(buf, 16)

	// LIST chunk (extra, should be skipped)
	buf = append(buf, []byte("LIST")...)
	buf = binary.LittleEndian.AppendUint32(buf, listChunkSize)
	buf = append(buf, listChunkData...)

	// data chunk
	buf = append(buf, []byte("data")...)
	buf = binary.LittleEndian.AppendUint32(buf, uint32(dataSize))
	for _, s := range samples {
		buf = binary.LittleEndian.AppendUint16(buf, uint16(s))
	}
	return buf
}
