package game

import (
	"testing"

	"github.com/google/uuid"

	"scrabble/backend/internal/engine"
)

// scriptIntn returns a drawIntn that yields the scripted indices in order, failing
// the test if the script is exhausted or an index is out of range. It lets a test
// drive the first-move draw deterministically (English SetupBag order: 9 'a' at
// 0..8, then 'b' …, blanks last).
func scriptIntn(t *testing.T, seq ...int) drawIntn {
	t.Helper()
	i := 0
	return func(n int) (int, error) {
		if i >= len(seq) {
			t.Fatalf("intn script exhausted (asked for [0,%d))", n)
		}
		v := seq[i]
		i++
		if v < 0 || v >= n {
			t.Fatalf("intn script value %d out of range [0,%d)", v, n)
		}
		return v, nil
	}
}

func TestSeedFirstMoveDirectWinner(t *testing.T) {
	a, b := uuid.New(), uuid.New()
	// a draws bag[0]='a' (rank 0); b draws bag[8]='b' (rank 1) → a wins, no tie.
	res, err := seedFirstMove(engine.VariantEnglish, []uuid.UUID{a, b}, scriptIntn(t, 0, 8))
	if err != nil {
		t.Fatalf("seedFirstMove: %v", err)
	}
	if res.winner != a {
		t.Fatalf("winner = %v, want %v", res.winner, a)
	}
	if got := res.order; len(got) != 2 || got[0] != a || got[1] != b {
		t.Fatalf("order = %v, want [a b]", got)
	}
	if len(res.draws) != 2 {
		t.Fatalf("draws = %d, want 2", len(res.draws))
	}
}

func TestSeedFirstMoveBlankSupersedes(t *testing.T) {
	a, b := uuid.New(), uuid.New()
	// a draws 'a' (rank 0); after the draw the two blanks sit at 97,98 — b draws
	// bag[97], a blank, which beats every letter.
	res, err := seedFirstMove(engine.VariantEnglish, []uuid.UUID{a, b}, scriptIntn(t, 0, 97))
	if err != nil {
		t.Fatalf("seedFirstMove: %v", err)
	}
	if res.winner != b {
		t.Fatalf("winner = %v, want %v (blank supersedes)", res.winner, b)
	}
	last := res.draws[len(res.draws)-1]
	if !last.Blank || last.Rank != engine.BlankRank || last.Letter != "?" {
		t.Fatalf("winning draw = %+v, want a blank (rank %d, '?')", last, engine.BlankRank)
	}
}

func TestSeedFirstMoveTieRedraw(t *testing.T) {
	a, b, c := uuid.New(), uuid.New(), uuid.New()
	// Round 1: a→bag[0]='a'(0), b→bag[0]='a'(0), c→bag[7]='b'(1) → a,b tie best.
	// Round 2 (a,b only): a→bag[0]='a'(0), b→bag[8]='b'(1) → a wins.
	res, err := seedFirstMove(engine.VariantEnglish, []uuid.UUID{a, b, c}, scriptIntn(t, 0, 0, 7, 0, 8))
	if err != nil {
		t.Fatalf("seedFirstMove: %v", err)
	}
	if res.winner != a {
		t.Fatalf("winner = %v, want %v", res.winner, a)
	}
	if len(res.draws) != 5 {
		t.Fatalf("draws = %d, want 5 (3 in round 1, 2 in round 2)", len(res.draws))
	}
	if res.draws[2].Round != 1 || res.draws[3].Round != 2 {
		t.Fatalf("round boundaries wrong: %+v", res.draws)
	}
	// The full table keeps every account's seating order, winner first.
	if got := res.order; got[0] != a || got[1] != b || got[2] != c {
		t.Fatalf("order = %v, want [a b c]", got)
	}
}

func TestSeedFirstMoveTooFewAccounts(t *testing.T) {
	if _, err := seedFirstMove(engine.VariantEnglish, []uuid.UUID{uuid.New()}, scriptIntn(t)); err == nil {
		t.Fatal("want error for a single account")
	}
}

func TestSeedFirstMovePerpetualTieCapped(t *testing.T) {
	a, b := uuid.New(), uuid.New()
	// Always drawing bag[0] gives both players an 'a' every round — a tie that never
	// resolves; the round cap must turn it into an error, not an infinite loop.
	alwaysZero := drawIntn(func(int) (int, error) { return 0, nil })
	if _, err := seedFirstMove(engine.VariantEnglish, []uuid.UUID{a, b}, alwaysZero); err == nil {
		t.Fatal("want error when ties never resolve")
	}
}

func TestRotateToFirst(t *testing.T) {
	a, b, c, d := uuid.New(), uuid.New(), uuid.New(), uuid.New()
	tests := []struct {
		name     string
		accounts []uuid.UUID
		winner   uuid.UUID
		want     []uuid.UUID
	}{
		{"two winner second", []uuid.UUID{a, b}, b, []uuid.UUID{b, a}},
		{"three winner first", []uuid.UUID{a, b, c}, a, []uuid.UUID{a, b, c}},
		{"four winner third", []uuid.UUID{a, b, c, d}, c, []uuid.UUID{c, d, a, b}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := rotateToFirst(tt.accounts, tt.winner)
			if len(got) != len(tt.want) {
				t.Fatalf("len = %d, want %d", len(got), len(tt.want))
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Fatalf("rotate = %v, want %v", got, tt.want)
				}
			}
		})
	}
}
