package qobuz

import (
	"encoding/json"
	"math/rand/v2"
	"strconv"
	"testing"
)

func TestParseYear(t *testing.T) {
	tests := []struct {
		in   string
		want int
	}{
		{"2021-05-14", 2021},
		{"1999", 1999},
		{"", 0},
		{"abc", 0},
		{"20", 0},
		{"19xy-01-01", 0},
	}
	for _, tt := range tests {
		if got := parseYear(tt.in); got != tt.want {
			t.Errorf("parseYear(%q) = %d, want %d", tt.in, got, tt.want)
		}
	}
}

func TestTrackArtist(t *testing.T) {
	withPerformer := apiTrack{Performer: apiArtist{Name: "Performer"}}
	if got := trackArtist(withPerformer, nil); got != "Performer" {
		t.Errorf("performer name: got %q want %q", got, "Performer")
	}

	album := &apiAlbum{Artist: apiArtist{Name: "AlbumArtist"}}
	if got := trackArtist(apiTrack{}, album); got != "AlbumArtist" {
		t.Errorf("album fallback: got %q want %q", got, "AlbumArtist")
	}

	if got := trackArtist(apiTrack{}, nil); got != "" {
		t.Errorf("no artist: got %q want empty", got)
	}
}

func TestDedupeTracksByID(t *testing.T) {
	tracks := []apiTrack{
		{ID: "1", Title: "first"},
		{ID: "2", Title: "second"},
		{ID: "1", Title: "dup of first"},
		{ID: "3", Title: "third"},
		{ID: "2", Title: "dup of second"},
		{ID: "", Title: "no id a"},
		{ID: "", Title: "no id b"},
	}

	got := dedupeTracksByID(tracks)

	want := []struct {
		id    string
		title string
	}{
		{"1", "first"}, // first occurrence wins
		{"2", "second"},
		{"3", "third"},
		{"", "no id a"}, // empty-ID tracks are always kept
		{"", "no id b"},
	}
	if len(got) != len(want) {
		t.Fatalf("got %d tracks, want %d", len(got), len(want))
	}
	for i, w := range want {
		if got[i].ID.String() != w.id || got[i].Title != w.title {
			t.Errorf("track %d = {%q, %q}, want {%q, %q}",
				i, got[i].ID.String(), got[i].Title, w.id, w.title)
		}
	}
}

func TestDedupeTracksByIDEmpty(t *testing.T) {
	if got := dedupeTracksByID(nil); len(got) != 0 {
		t.Errorf("dedupeTracksByID(nil) = %v, want empty", got)
	}
}

func TestSampleTracks(t *testing.T) {
	mk := func(n int) []apiTrack {
		ts := make([]apiTrack, n)
		for i := range ts {
			ts[i] = apiTrack{ID: json.Number(strconv.Itoa(i))}
		}
		return ts
	}
	idSet := func(ts []apiTrack) map[string]bool {
		m := make(map[string]bool, len(ts))
		for _, tr := range ts {
			m[tr.ID.String()] = true
		}
		return m
	}

	r := rand.New(rand.NewPCG(42, 1024))

	// Under the cap: every track is kept, but the list must still be shuffled.
	// This is the case that used to be returned in playlist order unchanged.
	in := mk(100)
	got := sampleTracks(in, 500, r.Shuffle)
	if len(got) != 100 {
		t.Fatalf("under cap: len = %d, want 100", len(got))
	}
	want := idSet(in)
	for _, tr := range got {
		if !want[tr.ID.String()] {
			t.Errorf("under cap: track %s not from input", tr.ID)
		}
	}
	sameOrder := true
	for i := range got {
		if got[i].ID != in[i].ID {
			sameOrder = false
			break
		}
	}
	if sameOrder {
		t.Error("under cap: list was not shuffled")
	}

	// Over the cap: exactly n tracks, all from the input, no duplicates.
	big := mk(1000)
	all := idSet(big)
	for range 20 {
		s := sampleTracks(big, 10, r.Shuffle)
		if len(s) != 10 {
			t.Fatalf("over cap: len = %d, want 10", len(s))
		}
		seen := make(map[string]bool, len(s))
		for _, tr := range s {
			id := tr.ID.String()
			if !all[id] {
				t.Fatalf("over cap: track %q not from input", id)
			}
			if seen[id] {
				t.Fatalf("over cap: duplicate track %q", id)
			}
			seen[id] = true
		}
	}
}

func TestNewQualityNormalization(t *testing.T) {
	for _, q := range []int{5, 6, 7, 27} {
		if got := New(q).quality; got != q {
			t.Errorf("New(%d).quality = %d, want %d", q, got, q)
		}
	}
	for _, q := range []int{0, 1, 99} {
		if got := New(q).quality; got != defaultQuality {
			t.Errorf("New(%d).quality = %d, want default %d", q, got, defaultQuality)
		}
	}
}
