package utils

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

// createTestWAVFile creates a minimal valid WAV file for testing
func createTestWAVFile(t *testing.T, dir string, filename string, options struct {
	duration      float64
	sampleRate    int
	channels      int
	bitsPerSample int
	comment       string
	artist        string
}) string {
	t.Helper()

	path := filepath.Join(dir, filename)
	file, err := os.Create(path)
	if err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}
	defer file.Close()

	// Calculate data chunk size based on duration
	bytesPerSample := options.bitsPerSample / 8
	samplesPerSecond := options.sampleRate * options.channels
	dataSize := int(options.duration * float64(samplesPerSecond*bytesPerSample))

	// Calculate file size (excluding RIFF header)
	fileSize := 4 + 8 + 16 + 8 + dataSize // WAVE + fmt chunk + data chunk header

	// Add LIST INFO chunk size if metadata provided
	var infoChunk []byte
	if options.comment != "" || options.artist != "" {
		infoChunk = buildINFOChunk(options.comment, options.artist)
		fileSize += 8 + len(infoChunk) // LIST chunk header + content
	}

	buf := &bytes.Buffer{}

	// Write RIFF header
	buf.WriteString("RIFF")
	binary.Write(buf, binary.LittleEndian, uint32(fileSize))
	buf.WriteString("WAVE")

	// Write fmt chunk
	buf.WriteString("fmt ")
	binary.Write(buf, binary.LittleEndian, uint32(16)) // chunk size
	binary.Write(buf, binary.LittleEndian, uint16(1))  // audio format (PCM)
	binary.Write(buf, binary.LittleEndian, uint16(options.channels))
	binary.Write(buf, binary.LittleEndian, uint32(options.sampleRate))
	byteRate := options.sampleRate * options.channels * bytesPerSample
	binary.Write(buf, binary.LittleEndian, uint32(byteRate))
	blockAlign := options.channels * bytesPerSample
	binary.Write(buf, binary.LittleEndian, uint16(blockAlign))
	binary.Write(buf, binary.LittleEndian, uint16(options.bitsPerSample))

	// Write LIST INFO chunk if metadata provided
	if len(infoChunk) > 0 {
		buf.WriteString("LIST")
		binary.Write(buf, binary.LittleEndian, uint32(len(infoChunk)))
		buf.Write(infoChunk)
	}

	// Write data chunk
	buf.WriteString("data")
	binary.Write(buf, binary.LittleEndian, uint32(dataSize))
	// Write silence for data
	buf.Write(make([]byte, dataSize))

	// Write to file
	if _, err := file.Write(buf.Bytes()); err != nil {
		t.Fatalf("Failed to write test file: %v", err)
	}

	return path
}

// buildINFOChunk builds a LIST INFO chunk with optional comment and artist
func buildINFOChunk(comment, artist string) []byte {
	buf := &bytes.Buffer{}
	buf.WriteString("INFO")

	if comment != "" {
		buf.WriteString("ICMT")
		// Size includes null terminator
		size := len(comment) + 1
		binary.Write(buf, binary.LittleEndian, uint32(size))
		buf.WriteString(comment)
		buf.WriteByte(0) // null terminator
		// Add padding byte if needed for word alignment
		if size%2 != 0 {
			buf.WriteByte(0)
		}
	}

	if artist != "" {
		buf.WriteString("IART")
		size := len(artist) + 1
		binary.Write(buf, binary.LittleEndian, uint32(size))
		buf.WriteString(artist)
		buf.WriteByte(0) // null terminator
		if size%2 != 0 {
			buf.WriteByte(0)
		}
	}

	return buf.Bytes()
}

func TestParseWAVHeader(t *testing.T) {
	// Create temporary directory for test files
	tmpDir := t.TempDir()

	t.Run("should parse basic WAV metadata", func(t *testing.T) {
		path := createTestWAVFile(t, tmpDir, "test_basic.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      60.0,
			sampleRate:    44100,
			channels:      2,
			bitsPerSample: 16,
			comment:       "",
			artist:        "",
		})

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		if metadata.SampleRate != 44100 {
			t.Errorf("SampleRate incorrect: got %d, want 44100", metadata.SampleRate)
		}

		if metadata.Channels != 2 {
			t.Errorf("Channels incorrect: got %d, want 2", metadata.Channels)
		}

		if metadata.BitsPerSample != 16 {
			t.Errorf("BitsPerSample incorrect: got %d, want 16", metadata.BitsPerSample)
		}

		// Duration should be approximately 60 seconds (allow small rounding error)
		if metadata.Duration < 59.9 || metadata.Duration > 60.1 {
			t.Errorf("Duration incorrect: got %f, want ~60.0", metadata.Duration)
		}
	})

	t.Run("should extract comment metadata", func(t *testing.T) {
		expectedComment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549"
		path := createTestWAVFile(t, tmpDir, "test_comment.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      10.0,
			sampleRate:    48000,
			channels:      1,
			bitsPerSample: 16,
			comment:       expectedComment,
			artist:        "",
		})

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		if metadata.Comment != expectedComment {
			t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, expectedComment)
		}
	})

	t.Run("should extract artist metadata", func(t *testing.T) {
		expectedArtist := "AudioMoth"
		path := createTestWAVFile(t, tmpDir, "test_artist.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      5.0,
			sampleRate:    48000,
			channels:      1,
			bitsPerSample: 16,
			comment:       "",
			artist:        expectedArtist,
		})

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		if metadata.Artist != expectedArtist {
			t.Errorf("Artist incorrect: got %q, want %q", metadata.Artist, expectedArtist)
		}
	})

	t.Run("should extract both comment and artist", func(t *testing.T) {
		expectedComment := "Test recording comment"
		expectedArtist := "Test Artist"
		path := createTestWAVFile(t, tmpDir, "test_both.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      15.0,
			sampleRate:    44100,
			channels:      2,
			bitsPerSample: 16,
			comment:       expectedComment,
			artist:        expectedArtist,
		})

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		if metadata.Comment != expectedComment {
			t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, expectedComment)
		}

		if metadata.Artist != expectedArtist {
			t.Errorf("Artist incorrect: got %q, want %q", metadata.Artist, expectedArtist)
		}
	})

	t.Run("should handle different sample rates", func(t *testing.T) {
		testCases := []struct {
			sampleRate int
		}{
			{8000},
			{16000},
			{22050},
			{44100},
			{48000},
			{96000},
		}

		for _, tc := range testCases {
			t.Run("", func(t *testing.T) {
				path := createTestWAVFile(t, tmpDir, "test_sr.wav", struct {
					duration      float64
					sampleRate    int
					channels      int
					bitsPerSample int
					comment       string
					artist        string
				}{
					duration:      1.0,
					sampleRate:    tc.sampleRate,
					channels:      1,
					bitsPerSample: 16,
					comment:       "",
					artist:        "",
				})

				metadata, err := ParseWAVHeader(path)
				if err != nil {
					t.Fatalf("Failed to parse WAV header: %v", err)
				}

				if metadata.SampleRate != tc.sampleRate {
					t.Errorf("SampleRate incorrect: got %d, want %d", metadata.SampleRate, tc.sampleRate)
				}
			})
		}
	})

	t.Run("should handle different channel counts", func(t *testing.T) {
		testCases := []struct {
			channels int
		}{
			{1}, // Mono
			{2}, // Stereo
		}

		for _, tc := range testCases {
			t.Run("", func(t *testing.T) {
				path := createTestWAVFile(t, tmpDir, "test_ch.wav", struct {
					duration      float64
					sampleRate    int
					channels      int
					bitsPerSample int
					comment       string
					artist        string
				}{
					duration:      1.0,
					sampleRate:    44100,
					channels:      tc.channels,
					bitsPerSample: 16,
					comment:       "",
					artist:        "",
				})

				metadata, err := ParseWAVHeader(path)
				if err != nil {
					t.Fatalf("Failed to parse WAV header: %v", err)
				}

				if metadata.Channels != tc.channels {
					t.Errorf("Channels incorrect: got %d, want %d", metadata.Channels, tc.channels)
				}
			})
		}
	})

	t.Run("should handle different bit depths", func(t *testing.T) {
		testCases := []struct {
			bitsPerSample int
		}{
			{8},
			{16},
			{24},
			{32},
		}

		for _, tc := range testCases {
			t.Run("", func(t *testing.T) {
				path := createTestWAVFile(t, tmpDir, "test_bits.wav", struct {
					duration      float64
					sampleRate    int
					channels      int
					bitsPerSample int
					comment       string
					artist        string
				}{
					duration:      1.0,
					sampleRate:    44100,
					channels:      1,
					bitsPerSample: tc.bitsPerSample,
					comment:       "",
					artist:        "",
				})

				metadata, err := ParseWAVHeader(path)
				if err != nil {
					t.Fatalf("Failed to parse WAV header: %v", err)
				}

				if metadata.BitsPerSample != tc.bitsPerSample {
					t.Errorf("BitsPerSample incorrect: got %d, want %d", metadata.BitsPerSample, tc.bitsPerSample)
				}
			})
		}
	})

	t.Run("should handle very short durations", func(t *testing.T) {
		path := createTestWAVFile(t, tmpDir, "test_short.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      0.1, // 100ms
			sampleRate:    44100,
			channels:      1,
			bitsPerSample: 16,
			comment:       "",
			artist:        "",
		})

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		if metadata.Duration < 0.09 || metadata.Duration > 0.11 {
			t.Errorf("Duration incorrect: got %f, want ~0.1", metadata.Duration)
		}
	})

	t.Run("should handle long durations", func(t *testing.T) {
		path := createTestWAVFile(t, tmpDir, "test_long.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      600.0, // 10 minutes
			sampleRate:    44100,
			channels:      1,
			bitsPerSample: 16,
			comment:       "",
			artist:        "",
		})

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		if metadata.Duration < 599.0 || metadata.Duration > 601.0 {
			t.Errorf("Duration incorrect: got %f, want ~600.0", metadata.Duration)
		}
	})

	t.Run("should return error for non-existent file", func(t *testing.T) {
		_, err := ParseWAVHeader("/nonexistent/file.wav")
		if err == nil {
			t.Error("Expected error for non-existent file")
		}
	})

	t.Run("should return error for non-WAV file", func(t *testing.T) {
		// Create a non-WAV file
		path := filepath.Join(tmpDir, "not_a_wav.txt")
		if err := os.WriteFile(path, []byte("This is not a WAV file"), 0644); err != nil {
			t.Fatalf("Failed to create test file: %v", err)
		}

		_, err := ParseWAVHeader(path)
		if err == nil {
			t.Error("Expected error for non-WAV file")
		}
	})

	t.Run("should return error for truncated file", func(t *testing.T) {
		// Create a file that's too small to be valid WAV
		path := filepath.Join(tmpDir, "truncated.wav")
		if err := os.WriteFile(path, []byte("RIFF"), 0644); err != nil {
			t.Fatalf("Failed to create test file: %v", err)
		}

		_, err := ParseWAVHeader(path)
		if err == nil {
			t.Error("Expected error for truncated file")
		}
	})

	t.Run("should handle empty metadata strings", func(t *testing.T) {
		path := createTestWAVFile(t, tmpDir, "test_empty.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      10.0,
			sampleRate:    44100,
			channels:      1,
			bitsPerSample: 16,
			comment:       "",
			artist:        "",
		})

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		if metadata.Comment != "" {
			t.Errorf("Comment should be empty, got %q", metadata.Comment)
		}

		if metadata.Artist != "" {
			t.Errorf("Artist should be empty, got %q", metadata.Artist)
		}
	})

	t.Run("should handle long comment strings", func(t *testing.T) {
		longComment := "Recorded at 21:00:00 24/02/2025 (UTC+13) by AudioMoth 248AB50153AB0549 at medium gain while battery was 4.3V and temperature was 15.8C. This is a very long comment with additional information about the recording session."

		path := createTestWAVFile(t, tmpDir, "test_long_comment.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      10.0,
			sampleRate:    44100,
			channels:      1,
			bitsPerSample: 16,
			comment:       longComment,
			artist:        "",
		})

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		if metadata.Comment != longComment {
			t.Errorf("Comment incorrect: got %q, want %q", metadata.Comment, longComment)
		}
	})

	t.Run("should extract file modification time", func(t *testing.T) {
		path := createTestWAVFile(t, tmpDir, "test_modtime.wav", struct {
			duration      float64
			sampleRate    int
			channels      int
			bitsPerSample int
			comment       string
			artist        string
		}{
			duration:      5.0,
			sampleRate:    44100,
			channels:      1,
			bitsPerSample: 16,
			comment:       "",
			artist:        "",
		})

		// Get expected mod time
		info, err := os.Stat(path)
		if err != nil {
			t.Fatalf("Failed to stat file: %v", err)
		}
		expectedModTime := info.ModTime()

		metadata, err := ParseWAVHeader(path)
		if err != nil {
			t.Fatalf("Failed to parse WAV header: %v", err)
		}

		// Allow 1 second tolerance for filesystem granularity
		diff := metadata.FileModTime.Sub(expectedModTime)
		if diff < -1*time.Second || diff > 1*time.Second {
			t.Errorf("FileModTime incorrect: got %v, want %v (diff: %v)",
				metadata.FileModTime, expectedModTime, diff)
		}

		// Ensure FileModTime is not zero
		if metadata.FileModTime.IsZero() {
			t.Error("FileModTime should not be zero")
		}
	})
}

func TestExtractNullTerminatedString(t *testing.T) {
	testCases := []struct {
		name     string
		input    []byte
		expected string
	}{
		{
			name:     "string with null terminator",
			input:    []byte{'h', 'e', 'l', 'l', 'o', 0, 'w', 'o', 'r', 'l', 'd'},
			expected: "hello",
		},
		{
			name:     "string without null terminator",
			input:    []byte{'h', 'e', 'l', 'l', 'o'},
			expected: "hello",
		},
		{
			name:     "empty string",
			input:    []byte{},
			expected: "",
		},
		{
			name:     "only null terminator",
			input:    []byte{0},
			expected: "",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := extractNullTerminatedString(tc.input)
			if result != tc.expected {
				t.Errorf("Result incorrect: got %q, want %q", result, tc.expected)
			}
		})
	}
}