package engine

import (
	"errors"
	"testing"

	"gitea.iliadenisov.ru/developer/scrabble-solver/board"
	"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
)

// TestRegistryOpensEveryVariant checks that Open loads all three variants at the
// requested version and reports them through Latest and Versions.
func TestRegistryOpensEveryVariant(t *testing.T) {
	for _, v := range Variants() {
		version, solver, err := testReg.Latest(v)
		if err != nil {
			t.Fatalf("latest %s: %v", v, err)
		}
		if version != testVersion {
			t.Errorf("latest %s version = %q, want %q", v, version, testVersion)
		}
		if solver == nil {
			t.Errorf("latest %s solver is nil", v)
		}
		if got := testReg.Versions(v); len(got) != 1 || got[0] != testVersion {
			t.Errorf("versions %s = %v, want [%q]", v, got, testVersion)
		}
	}
}

// TestRegistryValidatesKnownWords is the per-variant smoke test: a known word
// laid over the centre validates against the loaded dictionary, including the
// Эрудит variant.
func TestRegistryValidatesKnownWords(t *testing.T) {
	cases := []struct {
		variant Variant
		word    string
	}{
		{VariantEnglish, "cat"},
		{VariantRussianScrabble, "кот"},
		{VariantErudit, "кот"},
	}
	for _, tc := range cases {
		t.Run(tc.variant.String(), func(t *testing.T) {
			solver, err := testReg.Solver(tc.variant, testVersion)
			if err != nil {
				t.Fatalf("solver: %v", err)
			}
			rs := solver.Rules()
			row, col := centre(rs)
			ps := placementsForWord(t, rs, row, col, scrabble.Horizontal, tc.word)
			if _, err := solver.ValidatePlay(board.New(rs.Rows, rs.Cols), scrabble.Horizontal, ps); err != nil {
				t.Fatalf("validate %q against %s: %v", tc.word, tc.variant, err)
			}
		})
	}
}

// TestRegistryUnknownLookups covers the not-found error taxonomy.
func TestRegistryUnknownLookups(t *testing.T) {
	reg, err := Open(testDictDir(), testVersion, VariantEnglish)
	if err != nil {
		t.Fatalf("open scrabble_en-only registry: %v", err)
	}
	defer reg.Close()

	if _, err := reg.Solver(VariantEnglish, "absent"); !errors.Is(err, ErrUnknownVersion) {
		t.Errorf("solver with bad version: got %v, want ErrUnknownVersion", err)
	}
	if _, err := reg.Solver(VariantErudit, testVersion); !errors.Is(err, ErrUnknownVariant) {
		t.Errorf("solver for unloaded variant: got %v, want ErrUnknownVariant", err)
	}
	if _, _, err := reg.Latest(VariantErudit); !errors.Is(err, ErrUnknownVariant) {
		t.Errorf("latest for unloaded variant: got %v, want ErrUnknownVariant", err)
	}
	if got := reg.Versions(VariantErudit); got != nil {
		t.Errorf("versions for unloaded variant = %v, want nil", got)
	}
}

// TestRegistryCloseIdempotent verifies Close may be called more than once.
func TestRegistryCloseIdempotent(t *testing.T) {
	reg, err := Open(testDictDir(), testVersion, VariantEnglish)
	if err != nil {
		t.Fatalf("open: %v", err)
	}
	if err := reg.Close(); err != nil {
		t.Fatalf("first close: %v", err)
	}
	if err := reg.Close(); err != nil {
		t.Fatalf("second close: %v", err)
	}
}

// TestRegistryOpenMissingDir fails when a dictionary file is absent.
func TestRegistryOpenMissingDir(t *testing.T) {
	if _, err := Open(t.TempDir(), testVersion, VariantEnglish); err == nil {
		t.Fatal("expected an error opening a registry over an empty directory")
	}
}
