//go:build integration

package inttest

import (
	"context"
	"errors"
	"testing"

	"github.com/google/uuid"

	"scrabble/backend/internal/engine"
	"scrabble/backend/internal/game"
)

// createTwoSeatGame starts a plain two-human game of the given variant.
func createTwoSeatGame(t *testing.T, svc *game.Service, variant engine.Variant) game.Game {
	t.Helper()
	g, err := svc.Create(context.Background(), game.CreateParams{
		Variant:              variant,
		Seats:                []uuid.UUID{provisionAccount(t), provisionAccount(t)},
		HintsAllowed:         true,
		HintsPerPlayer:       1,
		MultipleWordsPerTurn: true,
	})
	if err != nil {
		t.Fatalf("create %s game: %v", variant, err)
	}
	return g
}

// TestNoRepeatWordsPinnedFromTheVariant checks the rule is written into games.no_repeat_words at
// creation from the variant — on for Erudit, off for both Scrabble variants — and read back with
// the game rather than re-derived.
func TestNoRepeatWordsPinnedFromTheVariant(t *testing.T) {
	svc := newGameService()
	for _, tc := range []struct {
		variant engine.Variant
		want    bool
	}{
		{engine.VariantErudit, true},
		{engine.VariantRussianScrabble, false},
		{engine.VariantEnglish, false},
	} {
		t.Run(tc.variant.String(), func(t *testing.T) {
			created := createTwoSeatGame(t, svc, tc.variant)
			if created.NoRepeatWords != tc.want {
				t.Errorf("created game NoRepeatWords = %v, want %v", created.NoRepeatWords, tc.want)
			}
			// Re-read it: the value must come from the row, not from the create call.
			reloaded, err := svc.GameByID(context.Background(), created.ID)
			if err != nil {
				t.Fatalf("get game: %v", err)
			}
			if reloaded.NoRepeatWords != tc.want {
				t.Errorf("reloaded game NoRepeatWords = %v, want %v", reloaded.NoRepeatWords, tc.want)
			}
		})
	}
}

// TestNoRepeatWordsHonoursThePinNotTheVariant is the compatibility guard for every Erudit game
// that existed before the rule: those rows carry no_repeat_words false (the migration's default),
// and such a game must keep playing unrestricted. Flipping the stored flag stands in for one.
func TestNoRepeatWordsHonoursThePinNotTheVariant(t *testing.T) {
	ctx := context.Background()
	svc := newGameService()

	// Each game draws from a bag seeded with crypto/rand, so an opening rack occasionally has no
	// playable word at all. That is a legitimate engine answer which says nothing about the pin
	// under test, and taking it for a failure made this test flake (~1 run in 300 on the committed
	// dictionary, and more often on another one). Retry with fresh games instead: one unlucky draw
	// no longer decides the result, while a genuinely broken path still fails every attempt.
	const draws = 5
	for attempt := 1; ; attempt++ {
		g := createTwoSeatGame(t, svc, engine.VariantErudit)
		if _, err := testDB.ExecContext(ctx,
			`UPDATE backend.games SET no_repeat_words = false WHERE game_id = $1`, g.ID); err != nil {
			t.Fatalf("clear the pin: %v", err)
		}

		reloaded, err := svc.GameByID(ctx, g.ID)
		if err != nil {
			t.Fatalf("get game: %v", err)
		}
		if reloaded.NoRepeatWords {
			t.Fatal("an Erudit game whose row has the rule off must report it off")
		}
		// The rule reaches the engine from the row, so the reconstructed game plays unrestricted:
		// its generated moves are the solver's full list, none filtered away.
		_, err = svc.Hint(ctx, reloaded.ID, reloaded.Seats[reloaded.ToMove].AccountID)
		if err == nil {
			return
		}
		if !errors.Is(err, game.ErrNoHintAvailable) {
			t.Fatalf("hint on an unrestricted Erudit game: %v", err)
		}
		if attempt == draws {
			t.Fatalf("no opening rack in %d games had a playable move", draws)
		}
	}
}
