package audio

import (
	"bytes"
	"encoding/binary"
	"testing"
)

func TestAudioDuration(t *testing.T) {
	t.Parallel()
	// 16k mono int16 ⇒ 2 bytes/sample ⇒ 32000 bytes/second.
	a := Audio{Format: PCM16kMono, Bytes: make([]byte, 32000)}
	if got, want := a.Duration(), 1.0; got != want {
		t.Fatalf("Duration: got %v, want %v", got, want)
	}
	// 4 seconds
	a.Bytes = make([]byte, 128000)
	if got, want := a.Duration(), 4.0; got != want {
		t.Fatalf("Duration: got %v, want %v", got, want)
	}
}

func TestAudioDurationEmptyAndBad(t *testing.T) {
	t.Parallel()
	if (Audio{}).Duration() != 0 {
		t.Fatalf("empty audio: Duration should be 0")
	}
	a := Audio{Format: Format{}, Bytes: make([]byte, 32000)}
	if a.Duration() != 0 {
		t.Fatalf("zero format: Duration should be 0")
	}
	a = Audio{Format: Format{SampleRate: 16000, Channels: 1, SampleBits: 16, Encoding: "opus"}, Bytes: make([]byte, 32000)}
	if a.Duration() != 0 {
		t.Fatalf("unsupported encoding: Duration should be 0")
	}
}

func TestFormatIsValid(t *testing.T) {
	t.Parallel()
	if !PCM16kMono.IsValid() {
		t.Fatalf("PCM16kMono should be valid")
	}
	if (Format{SampleRate: 8000, Channels: 1, SampleBits: 16, Encoding: "pcm_s16le"}).IsValid() {
		t.Fatalf("8k should be rejected")
	}
	if (Format{SampleRate: 16000, Channels: 2, SampleBits: 16, Encoding: "pcm_s16le"}).IsValid() {
		t.Fatalf("stereo should be rejected")
	}
}

func TestWAVRoundTrip(t *testing.T) {
	t.Parallel()
	// 0.5s of square wave (alternating samples) — sndfile/aplay can play it.
	const nsamp = 8000
	pcm := make([]byte, nsamp*2)
	for i := 0; i < nsamp; i++ {
		var v int16 = -16384
		if i%2 == 0 {
			v = 16384
		}
		binary.LittleEndian.PutUint16(pcm[i*2:], uint16(v))
	}
	wav, err := WAVFromPCM(PCM16kMono, pcm)
	if err != nil {
		t.Fatalf("WAVFromPCM: %v", err)
	}
	if len(wav) != 44+len(pcm) {
		t.Fatalf("wav length: got %d, want %d", len(wav), 44+len(pcm))
	}
	if string(wav[0:4]) != "RIFF" || string(wav[8:12]) != "WAVE" {
		t.Fatalf("missing RIFF/WAVE marker: %q", wav[0:12])
	}
	f, pcm2, err := PCMFromWAV(wav)
	if err != nil {
		t.Fatalf("PCMFromWAV: %v", err)
	}
	if !f.IsValid() {
		t.Fatalf("parsed format invalid: %+v", f)
	}
	if !bytes.Equal(pcm, pcm2) {
		t.Fatalf("PCM mismatch after round-trip: in=%d bytes, out=%d bytes", len(pcm), len(pcm2))
	}
}

// A LIST chunk sitting between fmt and data is common (arecord and ffmpeg both
// write one), and its payload is free text that can spell "data". The parser
// walks chunk headers, so the text is skipped and the real samples are read.
func TestPCMFromWAVSkipsLISTChunk(t *testing.T) {
	t.Parallel()
	pcm := []byte{1, 0, 2, 0, 3, 0, 4, 0}
	list := []byte("LIST")
	payload := []byte("INFOICMTdata is not here")
	list = binary.LittleEndian.AppendUint32(list, uint32(len(payload)))
	list = append(list, payload...)

	plain, err := WAVFromPCM(PCM16kMono, pcm)
	if err != nil {
		t.Fatalf("WAVFromPCM: %v", err)
	}
	wav := append([]byte{}, plain[:36]...)
	wav = append(wav, list...)
	wav = append(wav, plain[36:]...)
	binary.LittleEndian.PutUint32(wav[4:8], uint32(len(wav)-8))

	f, got, err := PCMFromWAV(wav)
	if err != nil {
		t.Fatalf("PCMFromWAV: %v", err)
	}
	if !f.IsValid() {
		t.Fatalf("parsed format invalid: %+v", f)
	}
	if !bytes.Equal(got, pcm) {
		t.Fatalf("PCM mismatch: got %v, want %v", got, pcm)
	}
}

func TestPCMFromWAVRejectsNonCanonical(t *testing.T) {
	t.Parallel()
	// too short
	if _, _, err := PCMFromWAV([]byte("RIFF")); err == nil {
		t.Fatalf("short input should error")
	}
	// bad RIFF marker
	bad := make([]byte, 44)
	copy(bad[0:4], []byte("RIFF"))
	copy(bad[8:12], []byte("XXXX"))
	if _, _, err := PCMFromWAV(bad); err == nil {
		t.Fatalf("non-WAVE marker should error")
	}
	// format code 3 (float), canonical otherwise
	wav, _ := WAVFromPCM(PCM16kMono, []byte{0, 0})
	binary.LittleEndian.PutUint16(wav[20:22], 3) // IEEE float, not PCM
	if _, _, err := PCMFromWAV(wav); err == nil {
		t.Fatalf("non-PCM format should error")
	}
}
