package playlists

import (
	"testing"

	"github.com/jackc/pgx/v5/pgtype"
)

func mixTrack(b byte, kind string) discoverTrack {
	return discoverTrack{ID: pgtype.UUID{Bytes: [16]byte{b}, Valid: true}, PickKind: kind}
}

func idsOf(pool []discoverTrack) []byte {
	out := make([]byte, len(pool))
	for i, t := range pool {
		out[i] = t.ID.Bytes[0]
	}
	return out
}

func TestRotateForDay_RotatesWithinTierBlocks(t *testing.T) {
	// Tiered pools arrive tier-ordered; rotation must vary the daily
	// slice WITHIN each tier without hoisting tier-3 filler above
	// tier-1's exact fits (#1267).
	pool := []discoverTrack{
		mixTrack(1, pickKindTier1), mixTrack(2, pickKindTier1), mixTrack(3, pickKindTier1),
		mixTrack(4, pickKindTier2), mixTrack(5, pickKindTier2),
		mixTrack(6, pickKindTier3),
	}
	u := pgtype.UUID{Bytes: [16]byte{42}, Valid: true}
	got := rotateForDay(pool, u, "2026-07-03")
	if len(got) != len(pool) {
		t.Fatalf("len = %d, want %d", len(got), len(pool))
	}
	wantKinds := []string{
		pickKindTier1, pickKindTier1, pickKindTier1,
		pickKindTier2, pickKindTier2, pickKindTier3,
	}
	for i, k := range wantKinds {
		if got[i].PickKind != k {
			t.Fatalf("pos %d kind = %q, want %q (tier order broken: %v)",
				i, got[i].PickKind, k, idsOf(got))
		}
	}
	// Each block is a rotation of its input: contiguity check for the
	// 3-element tier-1 block (successor relation preserved cyclically).
	wantOrder := map[byte]byte{1: 2, 2: 3, 3: 1}
	for i := 0; i < 2; i++ {
		if got[i+1].ID.Bytes[0] != wantOrder[got[i].ID.Bytes[0]] {
			t.Errorf("tier1 block is not a rotation: %v", idsOf(got[:3]))
			break
		}
	}
	// Determinism within a day.
	again := rotateForDay(pool, u, "2026-07-03")
	for i := range got {
		if got[i].ID != again[i].ID {
			t.Fatal("rotation must be deterministic for the same (user, day)")
		}
	}
}

func TestRotateForDay_UntieredPoolIsWholeRotation(t *testing.T) {
	// Untiered pools (all PickKind "") are one block — the original
	// whole-pool rotation semantics.
	pool := []discoverTrack{mixTrack(1, ""), mixTrack(2, ""), mixTrack(3, ""), mixTrack(4, "")}
	got := rotateForDay(pool, pgtype.UUID{Bytes: [16]byte{7}, Valid: true}, "2026-07-03")
	if len(got) != 4 {
		t.Fatalf("len = %d, want 4", len(got))
	}
	wantNext := map[byte]byte{1: 2, 2: 3, 3: 4, 4: 1}
	for i := 0; i < 3; i++ {
		cur, next := got[i].ID.Bytes[0], got[i+1].ID.Bytes[0]
		if wantNext[cur] != next {
			t.Errorf("not a rotation of the input: %v", idsOf(got))
			break
		}
	}
}

func TestFinishMix_PropagatesPickKind(t *testing.T) {
	// Tier stamps ride discoverTrack through diversify/truncate into
	// the rankedCandidates that insertSystemPlaylist persists (#1267).
	pool := []discoverTrack{mixTrack(1, pickKindTier1), mixTrack(2, pickKindTier2)}
	got := finishMix(pool, false)
	if len(got) != 2 {
		t.Fatalf("len = %d, want 2", len(got))
	}
	if got[0].PickKind != pickKindTier1 || got[1].PickKind != pickKindTier2 {
		t.Errorf("pick kinds = %q, %q — want tier1, tier2", got[0].PickKind, got[1].PickKind)
	}
}

func TestPickKindForMixTier(t *testing.T) {
	cases := []struct {
		tier int32
		want string
	}{{1, pickKindTier1}, {2, pickKindTier2}, {3, pickKindTier3}, {9, pickKindTier3}}
	for _, c := range cases {
		if got := pickKindForMixTier(c.tier); got != c.want {
			t.Errorf("pickKindForMixTier(%d) = %q, want %q", c.tier, got, c.want)
		}
	}
}
