package utils

import (
	"path/filepath"
	"testing"
	"time"
)

// mustGenerateID is a test helper that calls GenerateLongID and fatals on error.
func mustGenerateID(t *testing.T) string {
	t.Helper()
	id, err := GenerateLongID()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	return id
}

// isValidAlphabetChar checks if c is in the nanoid default alphabet (0-9, A-Z, a-z, _, -).
func isValidAlphabetChar(c rune) bool {
	return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c == '-'
}

func TestGenerateFileID(t *testing.T) {
	t.Run("generates 21-character ID", func(t *testing.T) {
		id := mustGenerateID(t)
		if len(id) != 21 {
			t.Errorf("expected length 21, got %d: %q", len(id), id)
		}
	})

	t.Run("uses only valid alphabet characters", func(t *testing.T) {
		id := mustGenerateID(t)
		for _, c := range id {
			if !isValidAlphabetChar(c) {
				t.Errorf("invalid character %q in ID %q", string(c), id)
			}
		}
	})

	t.Run("generates unique IDs", func(t *testing.T) {
		seen := make(map[string]bool)
		for range 100 {
			id := mustGenerateID(t)
			if seen[id] {
				t.Errorf("duplicate ID generated: %q", id)
			}
			seen[id] = true
		}
	})
}

// mustResolveTimestamp is a test helper that calls ResolveTimestamp and fatals on error.
func mustResolveTimestamp(t *testing.T, meta *WAVMetadata, filename, tz string, useModTime bool, preParsed *time.Time) *TimestampResult {
	t.Helper()
	result, err := ResolveTimestamp(meta, filename, tz, useModTime, preParsed)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	return result
}

func TestResolveTimestamp(t *testing.T) {
	t.Run("resolves AudioMoth timestamp", func(t *testing.T) {
		meta := &WAVMetadata{
			Comment: "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.",
			Artist:  "AudioMoth",
		}
		result := mustResolveTimestamp(t, meta, "20250224_210000.wav", "Pacific/Auckland", false, nil)
		if !result.IsAudioMoth {
			t.Error("expected IsAudioMoth to be true")
		}
		if result.MothData == nil {
			t.Error("expected MothData to be non-nil")
		}
		expectedUTC := time.Date(2025, 2, 24, 8, 0, 0, 0, time.UTC)
		if !result.Timestamp.UTC().Equal(expectedUTC) {
			t.Errorf("expected UTC timestamp %v, got %v", expectedUTC, result.Timestamp.UTC())
		}
	})

	t.Run("falls back to filename timestamp", func(t *testing.T) {
		result := mustResolveTimestamp(t, &WAVMetadata{}, "20250224_210000.wav", "Pacific/Auckland", false, nil)
		if result.IsAudioMoth {
			t.Error("expected IsAudioMoth to be false")
		}
		if result.Timestamp.IsZero() {
			t.Error("expected non-zero timestamp")
		}
	})

	t.Run("falls back to file mod time when enabled", func(t *testing.T) {
		modTime := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)
		meta := &WAVMetadata{FileModTime: modTime}
		result := mustResolveTimestamp(t, meta, "nopattern.wav", "Pacific/Auckland", true, nil)
		if !result.Timestamp.Equal(modTime) {
			t.Errorf("expected timestamp %v, got %v", modTime, result.Timestamp)
		}
	})

	t.Run("errors_on_no_timestamp", func(t *testing.T) {
		cases := []struct {
			name       string
			useModTime bool
		}{
			{"mod_time_disabled", false},
			{"no_file_mod_time", true},
		}
		for _, tc := range cases {
			t.Run(tc.name, func(t *testing.T) {
				_, err := ResolveTimestamp(&WAVMetadata{}, "nopattern.wav", "Pacific/Auckland", tc.useModTime, nil)
				if err == nil {
					t.Error("expected error when no timestamp available")
				}
			})
		}
	})

	t.Run("AudioMoth detected but parse fails falls back to filename", func(t *testing.T) {
		meta := &WAVMetadata{Comment: "AudioMoth garbage data"}
		result := mustResolveTimestamp(t, meta, "20250224_210000.wav", "Pacific/Auckland", false, nil)
		if !result.IsAudioMoth {
			t.Error("expected IsAudioMoth to be true (detected even if parse failed)")
		}
		if result.MothData != nil {
			t.Error("expected MothData to be nil since parsing failed")
		}
		if result.Timestamp.IsZero() {
			t.Error("expected non-zero timestamp from filename fallback")
		}
	})
}

func TestReadWAVSegmentSamples(t *testing.T) {
	tmpPath := filepath.Join(t.TempDir(), "segment_test.wav")

	// Create a simple WAV file with 4 samples
	err := WriteWAVFile(tmpPath, []float64{0.1, 0.2, 0.3, 0.4}, 8000)
	if err != nil {
		t.Fatalf("failed to create temp wav: %v", err)
	}

	// Test 1: Read specific segment (0.25s = 2 samples at 8000Hz)
	// Actually, 1 sample is 1/8000s. Let's just read the whole thing for the test to keep it simple and test the binary chunk reading logic
	samples, rate, err := ReadWAVSegmentSamples(tmpPath, 0, 0)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if rate != 8000 {
		t.Errorf("expected sample rate 8000, got %d", rate)
	}
	if len(samples) != 4 {
		t.Errorf("expected 4 samples, got %d", len(samples))
	}

	// Test 2: Helper ReadWAVSamples wrapper
	samples2, _, err := ReadWAVSamples(tmpPath)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(samples2) != 4 {
		t.Errorf("expected 4 samples, got %d", len(samples2))
	}
}

func TestProcessSingleFile(t *testing.T) {
	tmpPath := filepath.Join(t.TempDir(), "20240101_120000.wav")
	err := WriteWAVFile(tmpPath, []float64{0.0, 0.0}, 8000)
	if err != nil {
		t.Fatalf("failed to create temp wav: %v", err)
	}

	res, err := ProcessSingleFile(tmpPath, -41.0, 174.0, "Pacific/Auckland", false)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if res.SampleRate != 8000 {
		t.Errorf("expected sample rate 8000, got %d", res.SampleRate)
	}
	if res.Hash == "" {
		t.Error("expected non-empty hash")
	}
}

func TestParseWAVHeaderWithHash(t *testing.T) {
	tmpPath := filepath.Join(t.TempDir(), "hash_test.wav")
	err := WriteWAVFile(tmpPath, []float64{0.0, 0.0}, 8000)
	if err != nil {
		t.Fatalf("failed to create temp wav: %v", err)
	}

	meta, hash, err := ParseWAVHeaderWithHash(tmpPath)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if meta.SampleRate != 8000 {
		t.Errorf("expected 8000, got %d", meta.SampleRate)
	}
	if hash == "" {
		t.Error("expected non-empty hash")
	}
}