package holdem

import (
	"math/rand/v2"
	"testing"
)

// The reshape's own guard: a table with more than one human actually plays, and
// the chips still conserve when the person to act is not always seat zero.
//
// The solo suite proves the engine still behaves as it did; this proves the thing
// that changed. Two humans and two bots sit down, and the driver plays whichever
// human the action stops on — which is the whole point of the multiway advance:
// it runs the bots itself and hands control back at every *human* seat, not just
// at seat zero.

// randomMoveFor picks a legal move for a specific seat, the multiway sibling of
// randomMove. It never folds when it can check, so hands actually develop.
func randomMoveFor(s State, seat int, rng *rand.Rand) Move {
	owed := s.Owed(seat)
	var legal []Move
	if owed > 0 {
		legal = append(legal, Move{Kind: Fold}, Move{Kind: Call})
	} else {
		legal = append(legal, Move{Kind: Check})
	}
	if s.Seats[seat].Stack > owed && s.canBet() {
		if to := s.MinRaiseTo(seat); to < s.MaxRaiseTo(seat) {
			legal = append(legal, Move{Kind: Raise, To: to})
		}
	}
	return legal[rng.IntN(len(legal))]
}

func TestMultiwayChipsAreConserved(t *testing.T) {
	for game := 0; game < 100; game++ {
		rng := rand.New(rand.NewPCG(uint64(game), 71))
		tier := Tiers[game%len(Tiers)]

		// Two humans, two bots. The humans sit at 0 and 2 so the action genuinely
		// lands on a non-zero human seat, which is the case the old engine could not
		// have reached.
		seats := []SeatConfig{
			{Name: "Ana", Stack: tier.MaxBuy},
			{Name: "Bot A", Bot: true, Stack: tier.MaxBuy},
			{Name: "Bo", Stack: tier.MaxBuy},
			{Name: "Bot B", Bot: true, Stack: tier.MaxBuy},
		}
		s, _, err := New(tier, seats, tier.RakePct, uint64(game), 7)
		if err != nil {
			t.Fatalf("new table: %v", err)
		}
		want := chipsAt(s)

		for hand := 0; hand < 8 && s.Phase == PhaseHandOver; hand++ {
			var evs []Event
			s, evs, err = ApplyMove(s, 0, Move{Kind: Deal})
			if err != nil {
				t.Fatalf("game %d hand %d: deal: %v", game, hand, err)
			}
			want += reloaded(evs)
			check(t, s, want, game, hand, "deal")

			for step := 0; s.Phase == PhaseBetting; step++ {
				if step > 400 {
					t.Fatalf("game %d hand %d: the hand will not end", game, hand)
				}
				seat := s.ToAct
				if s.Seats[seat].Bot {
					t.Fatalf("game %d: advance stopped on bot seat %d — it should run bots itself", game, seat)
				}
				s, _, err = ApplyMove(s, seat, randomMoveFor(s, seat, rng))
				if err != nil {
					t.Fatalf("game %d hand %d seat %d: %v", game, hand, seat, err)
				}
				check(t, s, want, game, hand, "move")
			}
		}
	}
}

// TestMultiwayRejectsOutOfTurnMoves pins that a human cannot act when it is
// another human's turn — the betting move is legal only from the seat to act.
func TestMultiwayRejectsOutOfTurnMoves(t *testing.T) {
	tier := Tiers[0]
	seats := []SeatConfig{
		{Name: "Ana", Stack: tier.MaxBuy},
		{Name: "Bo", Stack: tier.MaxBuy},
	}
	s, _, err := New(tier, seats, tier.RakePct, 3, 9)
	if err != nil {
		t.Fatal(err)
	}
	s, _, err = ApplyMove(s, 0, Move{Kind: Deal})
	if err != nil {
		t.Fatal(err)
	}
	if s.Phase != PhaseBetting {
		t.Fatalf("want a live hand, got phase %s", s.Phase)
	}

	// Whoever is not to act tries to move. It must be refused, and nothing must
	// change.
	other := 1 - s.ToAct
	before := chipsAt(s)
	if _, _, err := ApplyMove(s, other, Move{Kind: Call}); err != ErrNotYourTurn {
		t.Fatalf("want ErrNotYourTurn from the seat not to act, got %v", err)
	}
	if got := chipsAt(s); got != before {
		t.Errorf("a refused move moved chips: %d -> %d", before, got)
	}
}
