package engine

import (
	"maps"
	"slices"
	"testing"

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

// allTiles returns the full multiset of tiles a bag is filled from, in ruleset
// order (letters then blanks).
func allTiles(rs *rules.Ruleset) []byte {
	var ts []byte
	for i, n := range rs.Counts {
		for range n {
			ts = append(ts, byte(i))
		}
	}
	for range rs.Blanks {
		ts = append(ts, blankTile)
	}
	return ts
}

// TestBagDeterministic checks that two bags with the same seed draw identically.
func TestBagDeterministic(t *testing.T) {
	rs := rules.English()
	a, b := NewBag(rs, 42), NewBag(rs, 42)
	if a.Len() != b.Len() {
		t.Fatalf("len mismatch: %d vs %d", a.Len(), b.Len())
	}
	for a.Len() > 0 {
		if da, db := a.Draw(3), b.Draw(3); !slices.Equal(da, db) {
			t.Fatalf("same seed drew differently: %v vs %v", da, db)
		}
	}
}

// TestBagReturnConservesMultiset checks Len accounting and that Return puts the
// exact tiles back, leaving the bag's multiset unchanged.
func TestBagReturnConservesMultiset(t *testing.T) {
	rs := rules.English()
	want := tileCounts(allTiles(rs))
	total := len(allTiles(rs))

	b := NewBag(rs, 7)
	if b.Len() != total {
		t.Fatalf("new bag len = %d, want %d", b.Len(), total)
	}
	drawn := b.Draw(rs.RackSize)
	if b.Len() != total-rs.RackSize {
		t.Fatalf("after draw len = %d, want %d", b.Len(), total-rs.RackSize)
	}
	b.Return(drawn)
	if b.Len() != total {
		t.Fatalf("after return len = %d, want %d", b.Len(), total)
	}
	if got := tileCounts(b.Draw(b.Len())); !maps.Equal(got, want) {
		t.Fatalf("multiset changed across draw/return")
	}
}

// TestBagDrawAll returns everything once the bag is exhausted and never panics.
func TestBagDrawAll(t *testing.T) {
	rs := rules.English()
	b := NewBag(rs, 1)
	all := b.Draw(b.Len() + 10) // asking for more than present returns all
	if len(all) != len(allTiles(rs)) {
		t.Fatalf("drew %d, want %d", len(all), len(allTiles(rs)))
	}
	if b.Len() != 0 {
		t.Fatalf("bag len = %d, want 0", b.Len())
	}
	if got := b.Draw(1); len(got) != 0 {
		t.Fatalf("draw from empty bag returned %d tiles", len(got))
	}
}
