package recommendation

import (
	"math/rand"
	"testing"
	"time"

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

	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)

func cand(id string, in ScoringInputs) Candidate {
	t := dbq.Track{Title: id}
	_ = t.ID.Scan("00000000-0000-0000-0000-" + id) // 12-char id padded
	return Candidate{Track: t, Inputs: in}
}

func TestShuffle_LikedRanksAboveUnliked(t *testing.T) {
	cs := []Candidate{
		cand("000000000001", ScoringInputs{IsGeneralLiked: false}),
		cand("000000000002", ScoringInputs{IsGeneralLiked: true}),
	}
	out := Shuffle(cs, defaultWeights(), time.Now(), fixedRNG(0.5), 10)
	if out[0].Track.Title != "000000000002" {
		t.Errorf("liked track did not rank first: %+v", out)
	}
}

func TestShuffle_HighSkipRanksLast(t *testing.T) {
	cs := []Candidate{
		cand("000000000001", ScoringInputs{PlayCount: 10, SkipCount: 10}), // ratio 1.0
		cand("000000000002", ScoringInputs{PlayCount: 10, SkipCount: 0}),  // ratio 0
		cand("000000000003", ScoringInputs{PlayCount: 10, SkipCount: 5}),  // ratio 0.5
	}
	out := Shuffle(cs, defaultWeights(), time.Now(), fixedRNG(0.5), 10)
	if out[0].Track.Title != "000000000002" || out[2].Track.Title != "000000000001" {
		t.Errorf("skip-ratio ordering broken: %v", titles(out))
	}
}

func TestShuffle_LimitTruncates(t *testing.T) {
	cs := make([]Candidate, 100)
	for i := range cs {
		cs[i] = cand("00000000000"+string(rune('a'+i%26)), ScoringInputs{})
	}
	out := Shuffle(cs, defaultWeights(), time.Now(), fixedRNG(0.5), 10)
	if len(out) != 10 {
		t.Errorf("len = %d, want 10", len(out))
	}
}

func TestShuffle_JitterDoesNotFlipStructuralWinner(t *testing.T) {
	// Liked vs unliked: even with many random RNG seeds, liked NEVER ranks below unliked.
	r := rand.New(rand.NewSource(42))
	for i := 0; i < 500; i++ {
		cs := []Candidate{
			cand("000000000001", ScoringInputs{IsGeneralLiked: false}),
			cand("000000000002", ScoringInputs{IsGeneralLiked: true}),
		}
		out := Shuffle(cs, defaultWeights(), time.Now(), r.Float64, 10)
		if out[0].Track.Title != "000000000002" {
			t.Fatalf("iter %d: liked did not rank first; out=%v", i, titles(out))
		}
	}
}

func TestShuffle_Empty_ReturnsEmpty(t *testing.T) {
	out := Shuffle(nil, defaultWeights(), time.Now(), fixedRNG(0.5), 10)
	if len(out) != 0 {
		t.Errorf("len = %d, want 0", len(out))
	}
}

func titles(cs []Candidate) []string {
	out := make([]string, 0, len(cs))
	for _, c := range cs {
		out = append(out, c.Track.Title)
	}
	return out
}

// pgtype-uuid placeholder: Candidate doesn't need a real UUID for these
// pure tests; the Track.Title field is the human-readable handle. Track ID
// is left as zero pgtype.UUID and never compared.
var _ pgtype.UUID
