package metadata

import (
	"os"
	"path/filepath"
	"testing"

	"yellowjacket/internal/testfixtures"
)

// testFlacFiles returns every .flac in the generated fixture library
// (`make testdata`).
//
// Sourced from the manifest rather than by walking test_data/, which
// used to sweep up the deliberately malformed fixtures — a zero-byte
// .flac is there to prove the scanner survives it, not to be handed to
// a duration parser.
func testFlacFiles(t *testing.T) []string {
	t.Helper()

	m := testfixtures.Load(t)

	var files []string

	for _, track := range m.Tracks {
		if track.Format == "flac" {
			files = append(files, m.Abs(track.Path))
		}
	}

	if len(files) == 0 {
		t.Skip("no .flac fixtures in the manifest")
	}

	return files
}

// TestGetFlacDuration_BasicParsing verifies that getFlacDuration
// returns a positive duration for every FLAC test fixture.
func TestGetFlacDuration_BasicParsing(t *testing.T) {
	for _, path := range testFlacFiles(t) {
		t.Run(filepath.Base(path), func(t *testing.T) {
			f, err := os.Open(path)
			if err != nil {
				t.Fatalf("open: %v", err)
			}

			defer func() { _ = f.Close() }()

			ms, props, err := getFlacDuration(f)
			if err != nil {
				t.Fatalf("getFlacDuration: %v", err)
			}

			if ms <= 0 {
				t.Errorf(
					"expected positive duration, got %d",
					ms,
				)
			}

			if props == nil {
				t.Fatal("expected non-nil AudioProperties")

				return
			}

			if props.SampleRate <= 0 {
				t.Errorf(
					"expected positive sample rate, got %d",
					props.SampleRate,
				)
			}

			if props.BitDepth <= 0 {
				t.Errorf(
					"expected positive bit depth, got %d",
					props.BitDepth,
				)
			}

			if props.Channels <= 0 {
				t.Errorf(
					"expected positive channels, got %d",
					props.Channels,
				)
			}

			t.Logf(
				"duration: %dms  rate: %dHz  depth: %d  ch: %d",
				ms, props.SampleRate, props.BitDepth,
				props.Channels,
			)
		})
	}
}

// TestGetFlacDuration_MatchesBeepDecode verifies that the fast
// header-only parser produces a duration within 1 second of the full
// decode via beep, for every FLAC test fixture.
func TestGetFlacDuration_MatchesBeepDecode(t *testing.T) {
	for _, path := range testFlacFiles(t) {
		t.Run(filepath.Base(path), func(t *testing.T) {
			refMS, err := GetTrackLengthMillis(path)
			if err != nil {
				t.Fatalf("beep decode failed: %v", err)
			}

			f, err := os.Open(path)
			if err != nil {
				t.Fatalf("open: %v", err)
			}

			defer func() { _ = f.Close() }()

			fastMS, _, err := getFlacDuration(f)
			if err != nil {
				t.Fatalf("getFlacDuration: %v", err)
			}

			diffMS := refMS - fastMS
			if diffMS < 0 {
				diffMS = -diffMS
			}

			const toleranceMS = 1000

			t.Logf(
				"beep=%dms  fast=%dms  diff=%dms",
				refMS, fastMS, diffMS,
			)

			if diffMS > toleranceMS {
				t.Errorf(
					"duration mismatch: beep=%dms "+
						"fast=%dms (diff %dms "+
						"exceeds %dms tolerance)",
					refMS, fastMS, diffMS, toleranceMS,
				)
			}
		})
	}
}

// TestGetFlacDuration_WithPrependedID3v2 creates a temporary FLAC
// file with a synthetic ID3v2 tag prepended and verifies that
// getFlacDuration correctly skips it and parses the duration.
func TestGetFlacDuration_WithPrependedID3v2(t *testing.T) {
	files := testFlacFiles(t)

	// Use the first test fixture as our source.
	src := files[0]

	srcData, err := os.ReadFile(src)
	if err != nil {
		t.Fatalf("reading source: %v", err)
	}

	// Build a minimal ID3v2.3 header with 256 bytes of padding.
	//nolint:mnd // synthetic tag construction.
	paddingSize := 256

	id3Header := buildID3v2Header(paddingSize)

	// Write: ID3v2 header + padding + original FLAC data.
	tmpDir := t.TempDir()
	tmpPath := filepath.Join(tmpDir, "test_id3v2.flac")

	out := make([]byte, 0, len(id3Header)+paddingSize+len(srcData))
	out = append(out, id3Header...)
	out = append(out, make([]byte, paddingSize)...)
	out = append(out, srcData...)

	if err := os.WriteFile(tmpPath, out, 0o644); err != nil {
		t.Fatalf("writing temp file: %v", err)
	}

	// Get reference duration from original file.
	origF, err := os.Open(src)
	if err != nil {
		t.Fatalf("open original: %v", err)
	}

	defer func() { _ = origF.Close() }()

	origMS, _, err := getFlacDuration(origF)
	if err != nil {
		t.Fatalf("getFlacDuration on original: %v", err)
	}

	// Parse the ID3v2-wrapped file.
	tmpF, err := os.Open(tmpPath)
	if err != nil {
		t.Fatalf("open temp: %v", err)
	}

	defer func() { _ = tmpF.Close() }()

	wrappedMS, _, err := getFlacDuration(tmpF)
	if err != nil {
		t.Fatalf(
			"getFlacDuration on ID3v2-wrapped file: %v", err,
		)
	}

	if origMS != wrappedMS {
		t.Errorf(
			"duration mismatch: original=%dms wrapped=%dms",
			origMS, wrappedMS,
		)
	}

	t.Logf(
		"original=%dms  wrapped=%dms", origMS, wrappedMS,
	)
}

// TestParseFlacStreamInfo verifies the bit-level parsing of sample
// rate and total samples from a known StreamInfo block.
func TestParseFlacStreamInfo(t *testing.T) {
	// Construct a 34-byte StreamInfo with known values.
	// Layout of bytes 10-17 (64 bits, big-endian):
	//   bits  0-19: sample rate     (20 bits)
	//   bits 20-22: channels - 1    (3 bits)
	//   bits 23-27: bps - 1         (5 bits)
	//   bits 28-63: total samples   (36 bits)
	//
	// Test values:
	//   sample rate  = 44100  (0x0AC44)
	//   channels     = 2      (stored as 1, 0b001)
	//   bps          = 16     (stored as 15, 0b01111)
	//   total samples = 11614366 (0x00B1389E)
	//
	// Packed: 0x0AC442F000B1389E
	//   byte 10 = 0x0A   byte 14 = 0x00
	//   byte 11 = 0xC4   byte 15 = 0xB1
	//   byte 12 = 0x42   byte 16 = 0x38
	//   byte 13 = 0xF0   byte 17 = 0x9E
	//
	//nolint:mnd // byte values from manual FLAC spec packing.
	var si [streamInfoLength]byte

	si[10] = 0x0A
	si[11] = 0xC4
	si[12] = 0x42
	si[13] = 0xF0
	si[14] = 0x00
	si[15] = 0xB1
	si[16] = 0x38
	si[17] = 0x9E

	sr, total, ch, bps := parseFlacStreamInfo(si)

	//nolint:mnd // expected test values.
	const (
		wantSR       = 44100
		wantTotal    = 11614366
		wantChannels = 2
		wantBPS      = 16
	)

	if sr != wantSR {
		t.Errorf("sample rate: got %d, want %d", sr, wantSR)
	}

	if total != wantTotal {
		t.Errorf(
			"total samples: got %d, want %d",
			total, wantTotal,
		)
	}

	if ch != wantChannels {
		t.Errorf(
			"channels: got %d, want %d", ch, wantChannels,
		)
	}

	if bps != wantBPS {
		t.Errorf(
			"bits per sample: got %d, want %d", bps, wantBPS,
		)
	}
}

// buildID3v2Header creates a minimal 10-byte ID3v2.3 header with
// the given payload size encoded as a syncsafe integer.
//
//nolint:mnd // byte offsets from the ID3v2 spec.
func buildID3v2Header(payloadSize int) []byte {
	header := []byte{
		'I', 'D', '3', // signature
		3, 0, // version 2.3.0
		0,          // flags
		0, 0, 0, 0, // size (syncsafe, filled below)
	}

	header[6] = byte((payloadSize >> 21) & 0x7F)
	header[7] = byte((payloadSize >> 14) & 0x7F)
	header[8] = byte((payloadSize >> 7) & 0x7F)
	header[9] = byte(payloadSize & 0x7F)

	return header
}
