package main

import "testing"

func TestOneOhOneDealsCorrectly(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
	for i, p := range g.Players {
		want := 5
		if i == g.DealerIdx {
			want = 4
		}
		if len(p.Hand) != want {
			t.Errorf("игрок %d: ожидалось %d карт, получено %d", i, want, len(p.Hand))
		}
	}
	if len(g.Discard) != 1 {
		t.Fatalf("ожидалась 1 карта в сбросе (стартовая), получено %d", len(g.Discard))
	}
	if len(g.Stock) != 36-14-1 {
		t.Fatalf("ожидалось %d карт в колоде добора, получено %d", 36-14-1, len(g.Stock))
	}
	if g.CurrentPlayerIdx != g.nextActive(g.DealerIdx) {
		t.Errorf("первым должен ходить игрок слева от сдающего")
	}
}

// --- Дама: играется на что угодно + объявление масти ------------------

func TestQueenPlayableRegardlessOfSuitOrRank(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Queen, Hearts), c(Two, Diamonds)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Queen, Hearts)); err != nil {
		t.Fatalf("дама должна была сыграться независимо от масти и ранга: %v", err)
	}
	if g.Phase != OneOhOnePhaseChooseSuit {
		t.Fatalf("после дамы должна была начаться фаза выбора масти, получено %v", g.Phase)
	}
}

func TestChooseSuitSetsDeclaredSuitAndAdvancesTurn(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Queen, Hearts), c(Two, Diamonds)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay
	g.PlayCard(c(Queen, Hearts))

	if err := g.ChooseSuit(Spades); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.DeclaredSuit == nil || *g.DeclaredSuit != Spades {
		t.Errorf("ожидалась объявленная масть Spades, получено %v", g.DeclaredSuit)
	}
	if g.Phase != OneOhOnePhasePlay {
		t.Errorf("после выбора масти должна возобновиться обычная фаза игры")
	}
	if g.CurrentPlayerIdx != 1 {
		t.Errorf("ход должен был перейти следующему игроку, получено %d", g.CurrentPlayerIdx)
	}
}

func TestChooseSuitRejectedOutsidePhase(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Phase = OneOhOnePhasePlay
	if err := g.ChooseSuit(Spades); err != ErrOOOWrongPhase {
		t.Errorf("ожидалась ошибка недопустимой фазы, получено: %v", err)
	}
}

func TestChooseSuitRejectsInvalidSuit(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Phase = OneOhOnePhaseChooseSuit
	if err := g.ChooseSuit(Suit(99)); err != ErrOOOInvalidSuit {
		t.Errorf("ожидалась ошибка недопустимой масти, получено: %v", err)
	}
}

func TestDeclaredSuitGovernsNextPlayThenClears(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Queen, Hearts), c(Two, Clubs)} // вторая карта — чтобы рука не опустела после дамы
	g.Players[1].Hand = []Card{c(Five, Spades), c(Six, Diamonds)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay
	if err := g.PlayCard(c(Queen, Hearts)); err != nil {
		t.Fatalf("неожиданная ошибка при игре дамы: %v", err)
	}
	if err := g.ChooseSuit(Spades); err != nil {
		t.Fatalf("неожиданная ошибка при выборе масти: %v", err)
	}

	if !g.canPlay(c(Five, Spades)) {
		t.Fatalf("пятёрка пик должна была подойти под объявленную масть")
	}
	if g.canPlay(c(Six, Diamonds)) {
		t.Fatalf("шестёрка бубён не должна была подойти — не та объявленная масть и не совпадает с дамой по рангу")
	}

	if err := g.PlayCard(c(Five, Spades)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.DeclaredSuit != nil {
		t.Errorf("объявленная масть должна была очиститься после следующего хода")
	}
}

// --- Восьмёрка: обязательное покрытие ----------------------------------

func TestEightCreatesPendingObligation(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Diamonds)} // совпадает по масти с восьмёркой бубён
	g.PendingEightSuit = nil
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Eight, Diamonds), c(Two, Spades)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Eight, Diamonds)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.PendingEightSuit == nil || *g.PendingEightSuit != Diamonds {
		t.Fatalf("ожидалось висящее требование покрытия по бубнам, получено %v", g.PendingEightSuit)
	}
}

func TestPendingEightOnlySameSuitOrAnotherEightValid(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	suit := Diamonds
	g.PendingEightSuit = &suit

	if !g.canPlay(c(Two, Diamonds)) {
		t.Errorf("карта той же масти, что и висящая восьмёрка, должна быть годной")
	}
	if !g.canPlay(c(Eight, Clubs)) {
		t.Errorf("любая другая восьмёрка должна быть годной для покрытия")
	}
	if g.canPlay(c(King, Hearts)) {
		t.Errorf("карта другой масти (не восьмёрка) не должна подходить, пока висит требование покрытия")
	}
	if g.canPlay(c(Queen, Hearts)) {
		t.Errorf("дама НЕ должна автоматически покрывать висящую восьмёрку — приоритет у требования покрытия")
	}
}

func TestPendingEightChainContinuesWithNewSuit(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Diamonds)}
	g.PendingEightSuit = nil
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Eight, Diamonds), c(Two, Clubs)} // вторая карта — чтобы рука не опустела после восьмёрки
	g.Players[1].Hand = []Card{c(Eight, Spades), c(Two, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay
	if err := g.PlayCard(c(Eight, Diamonds)); err != nil {
		t.Fatalf("неожиданная ошибка первого хода: %v", err)
	}

	if err := g.PlayCard(c(Eight, Spades)); err != nil {
		t.Fatalf("другая восьмёрка должна была покрыть первую: %v", err)
	}
	if g.PendingEightSuit == nil || *g.PendingEightSuit != Spades {
		t.Errorf("требование покрытия должно было переключиться на масть новой восьмёрки (пики), получено %v", g.PendingEightSuit)
	}
}

func TestPendingEightClearedByMatchingSuitCover(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Diamonds)}
	g.PendingEightSuit = nil
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Eight, Diamonds), c(Two, Clubs)} // вторая карта — чтобы рука не опустела после восьмёрки
	g.Players[1].Hand = []Card{c(Two, Diamonds), c(Five, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay
	if err := g.PlayCard(c(Eight, Diamonds)); err != nil {
		t.Fatalf("неожиданная ошибка первого хода: %v", err)
	}

	if err := g.PlayCard(c(Two, Diamonds)); err != nil {
		t.Fatalf("карта той же масти должна была покрыть восьмёрку: %v", err)
	}
	if g.PendingEightSuit != nil {
		t.Errorf("требование покрытия должно было сняться после покрытия обычной картой той же масти")
	}
}

func TestPendingEightForcesRepeatedDraw(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Diamonds)}
	g.PendingEightSuit = nil
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Eight, Diamonds), c(Two, Clubs)} // вторая карта — чтобы рука не опустела после восьмёрки
	g.Players[1].Hand = []Card{c(Two, Hearts)}                    // не подходит под требование покрытия
	g.Stock = []Card{c(Three, Diamonds), c(Four, Clubs)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay
	if err := g.PlayCard(c(Eight, Diamonds)); err != nil {
		t.Fatalf("неожиданная ошибка первого хода: %v", err)
	}
	if g.CurrentPlayerIdx != 1 {
		t.Fatalf("тест предполагает, что ход перешёл ко второму игроку")
	}

	if err := g.Draw(); err != nil {
		t.Fatalf("неожиданная ошибка при доборе: %v", err)
	}
	if len(g.Players[1].Hand) != 1 {
		t.Fatalf("взятая тройка бубён подходит под покрытие и должна была сразу сыграться, рука: %v", g.Players[1].Hand)
	}
	if g.PendingEightSuit != nil {
		t.Errorf("требование покрытия должно было сняться после автосыгранной тройки бубён")
	}
}

func TestOpeningEightCardCreatesInitialObligation(t *testing.T) {
	found := false
	for attempt := 0; attempt < 300 && !found; attempt++ {
		g := NewOneOhOneGame([]string{"A", "B"}, -1)
		if g.topDiscard().Rank != Eight {
			continue
		}
		found = true
		wantSuit := g.topDiscard().Suit
		if g.PendingEightSuit == nil || *g.PendingEightSuit != wantSuit {
			t.Fatalf("стартовая карта — восьмёрка (%v), но требование покрытия не выставилось: %v", wantSuit, g.PendingEightSuit)
		}
	}
	if !found {
		t.Skip("за 300 попыток стартовая карта ни разу не оказалась восьмёркой — статистически возможно, но маловероятно")
	}
}

func TestPlayCardMatchingSuit(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ten, Clubs)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Ten, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.topDiscard() != (c(Ten, Clubs)) {
		t.Errorf("верхняя карта сброса должна была смениться")
	}
}

func TestPlayCardMatchingRank(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Nine, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Nine, Hearts)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
}

func TestPlayCardRejectsMismatch(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ten, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Ten, Hearts)); err != ErrOOOInvalidPlay {
		t.Errorf("ожидалась ошибка ErrOOOInvalidPlay, получено: %v", err)
	}
}

func TestDrawRejectedWhenPlayableCardExists(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Nine, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.Draw(); err != ErrOOOMustPlay {
		t.Errorf("ожидалась ошибка ErrOOOMustPlay, получено: %v", err)
	}
}

func TestDrawAutoPlaysWhenDrawnCardMatches(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ten, Hearts)} // не подходит
	g.Stock = []Card{c(Nine, Diamonds), c(Two, Clubs)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.Draw(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.topDiscard() != (c(Nine, Diamonds)) {
		t.Errorf("взятая подходящая карта должна была сразу сыграться")
	}
	if hasCard(g.Players[0].Hand, c(Nine, Diamonds)) {
		t.Errorf("сыгранной карты не должно остаться в руке")
	}
}

func TestAceSkipsNextPlayer(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ace, Clubs), c(Two, Diamonds)}
	g.Players[1].Hand = []Card{c(Ten, Hearts)}
	g.Players[2].Hand = []Card{c(Jack, Spades)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Ace, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.CurrentPlayerIdx != 2 {
		t.Errorf("туз должен был пропустить игрока 1, ожидался ход игрока 2, получено %d", g.CurrentPlayerIdx)
	}
	if len(g.Players[1].Hand) != 1 {
		t.Errorf("пропущенный игрок не должен был брать штрафные карты")
	}
}

func TestSevenGivesTwoPenaltyCardsAndSkips(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Seven, Clubs), c(Two, Diamonds)}
	g.Players[1].Hand = []Card{c(Ten, Hearts)}
	g.Players[2].Hand = []Card{c(Jack, Spades)}
	g.Stock = []Card{c(Three, Hearts), c(Four, Hearts), c(Five, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Seven, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Players[1].Hand) != 3 {
		t.Errorf("игрок 1 должен был взять 2 штрафные карты (1+2=3), получено %d карт", len(g.Players[1].Hand))
	}
	if g.CurrentPlayerIdx != 2 {
		t.Errorf("ход должен был перейти к игроку 2, получено %d", g.CurrentPlayerIdx)
	}
}

func TestKingOfSpadesGivesFourPenaltyCards(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Spades)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(King, Spades), c(Two, Diamonds)}
	g.Players[1].Hand = []Card{c(Ten, Hearts)}
	g.Stock = make([]Card, 0, 4)
	for i := 0; i < 4; i++ {
		g.Stock = append(g.Stock, c(Rank(2+i), Hearts))
	}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(King, Spades)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Players[1].Hand) != 5 {
		t.Errorf("игрок 1 должен был взять 4 штрафные карты (1+4=5), получено %d", len(g.Players[1].Hand))
	}
}

func TestOtherKingsHaveNoSpecialEffect(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
	g.Discard = []Card{c(Nine, Hearts)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(King, Hearts), c(Two, Diamonds)}
	g.Players[1].Hand = []Card{c(Ten, Hearts)}
	g.Players[2].Hand = []Card{c(Jack, Spades)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(King, Hearts)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.CurrentPlayerIdx != 1 {
		t.Errorf("некозырной король не должен ничего пропускать, ожидался ход игрока 1, получено %d", g.CurrentPlayerIdx)
	}
	if len(g.Players[1].Hand) != 1 {
		t.Errorf("игрок 1 не должен был брать штрафные карты")
	}
}

func TestRoundEndScoresRemainingCards(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ten, Clubs)}
	g.Players[1].Hand = []Card{c(King, Hearts), c(Ace, Diamonds)} // 4+11=15
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Ten, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Phase != OneOhOnePhaseRoundOver {
		t.Fatalf("раунд должен был завершиться победой игрока 0")
	}
	if g.Result.Winner != 0 {
		t.Fatalf("ожидался победитель 0, получен %d", g.Result.Winner)
	}
	if g.Result.ScoreDeltas[0] != 0 {
		t.Errorf("победитель без дамы должен получить 0 очков за раунд, получено %d", g.Result.ScoreDeltas[0])
	}
	if g.Result.ScoreDeltas[1] != 15 {
		t.Errorf("ожидалось 15 штрафных очков у проигравшего, получено %d", g.Result.ScoreDeltas[1])
	}
	if g.Players[1].Score != 15 {
		t.Errorf("общий счёт игрока 1 должен был стать 15, получено %d", g.Players[1].Score)
	}
}

func TestQueenBonusOnWin(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Queen, Clubs)}
	g.Players[1].Hand = []Card{c(Ten, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Queen, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if !g.Result.QueenBonus {
		t.Fatalf("ожидался бонус за даму")
	}
	if g.Result.ScoreDeltas[0] != -20 {
		t.Errorf("ожидался бонус -20 за некозырную даму, получено %d", g.Result.ScoreDeltas[0])
	}
}

func TestQueenOfHeartsGivesDoubleBonus(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Hearts)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Queen, Hearts)}
	g.Players[1].Hand = []Card{c(Ten, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Queen, Hearts)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Result.ScoreDeltas[0] != -40 {
		t.Errorf("ожидался бонус -40 за червовую даму, получено %d", g.Result.ScoreDeltas[0])
	}
}

func TestExactly101ResetsToZero(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Players[1].Score = 90 // 90+11(туз)=101
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ten, Clubs)}
	g.Players[1].Hand = []Card{c(Ace, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Ten, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Players[1].Score != 0 {
		t.Errorf("ровно 101 должно было обнулить счёт, получено %d", g.Players[1].Score)
	}
	if len(g.Result.Reset) != 1 || g.Result.Reset[0] != 1 {
		t.Errorf("ожидался Reset=[1], получено %v", g.Result.Reset)
	}
	if g.Players[1].Out {
		t.Errorf("игрок с обнулённым счётом не должен выбывать")
	}
}

func TestOver101Eliminates(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
	g.Players[1].Score = 95 // 95+11=106 > 101
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ten, Clubs)}
	g.Players[1].Hand = []Card{c(Ace, Hearts)}
	g.Players[2].Hand = []Card{c(Jack, Spades)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Ten, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if !g.Players[1].Out {
		t.Errorf("игрок с 106 очками должен был выбыть")
	}
	if g.Result.MatchOver {
		t.Errorf("партия не должна была завершиться (остались 2 активных игрока)")
	}
}

func TestMatchEndsWhenOneActivePlayerRemains(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Players[1].Score = 95
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ten, Clubs)}
	g.Players[1].Hand = []Card{c(Ace, Hearts)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if err := g.PlayCard(c(Ten, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if !g.Result.MatchOver {
		t.Fatalf("партия должна была завершиться (остался 1 активный игрок)")
	}
	if g.Result.MatchWinner != 0 {
		t.Errorf("ожидался победитель партии 0, получено %d", g.Result.MatchWinner)
	}
}

func TestNextRoundDealerIsRoundWinner(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
	g.Discard = []Card{c(Nine, Clubs)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Players[0].Hand = []Card{c(Ten, Clubs)}
	g.Players[1].Hand = []Card{c(Ten, Hearts)}
	g.Players[2].Hand = []Card{c(Jack, Spades)}
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay
	if err := g.PlayCard(c(Ten, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.DealerIdx != 0 {
		t.Fatalf("сдающим следующего раунда должен стать победитель (0), получено %d", g.DealerIdx)
	}

	g.NextRound()
	if g.Phase != OneOhOnePhasePlay {
		t.Fatalf("новый раунд должен был начаться")
	}
	for i, p := range g.Players {
		want := 5
		if i == g.DealerIdx {
			want = 4
		}
		if len(p.Hand) != want {
			t.Errorf("игрок %d: ожидалось %d карт в новом раунде, получено %d", i, want, len(p.Hand))
		}
	}
}

func TestStockReshufflesFromDiscardWhenEmpty(t *testing.T) {
	g := NewOneOhOneGame([]string{"A", "B"}, -1)
	g.Discard = []Card{c(Nine, Clubs), c(Ten, Diamonds), c(Jack, Diamonds)}
	g.PendingEightSuit = nil // сбрасываем возможное наследие случайной начальной раздачи
	g.DeclaredSuit = nil
	g.Stock = nil
	g.Players[0].Hand = []Card{c(Eight, Clubs)} // не подходит ни по масти (clubs vs diamonds), ни по рангу (8 vs J)
	g.CurrentPlayerIdx = 0
	g.Phase = OneOhOnePhasePlay

	if g.stockExhausted() {
		t.Fatalf("колода не должна считаться исчерпанной — под верхней картой сброса есть ещё карты")
	}
	if err := g.Draw(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Discard) > 2 {
		t.Errorf("после перетасовки в сбросе не должно накопиться старых карт, получено %d карт", len(g.Discard))
	}
}

// TestPendingEightInvariantHoldsAcrossManyFullGames — регрессионный
// стресс-тест: проверяет через множество полных партий, что
// canPlay всегда точно соответствует правилу "пока висит непокрытая
// восьмёрка, годится только карта её масти или другая восьмёрка".
// Написан после сообщения о подозрении, что требование покрытия
// не работает — сама логика оказалась корректной (проблема была в
// том, что интерфейс никак не показывал это требование игроку, см.
// oneohone.pending_eight_line), но тест оставлен как постоянная
// защита от регрессии этого конкретного правила.
func TestPendingEightInvariantHoldsAcrossManyFullGames(t *testing.T) {
	bot := OneOhOneBot{}
	for game := 0; game < 200; game++ {
		g := NewOneOhOneGame([]string{"A", "B", "C"}, -1)
		for turns := 0; turns < 2000 && g.Phase != OneOhOnePhaseRoundOver; turns++ {
			if g.Phase == OneOhOnePhaseChooseSuit {
				g.ChooseSuit(bot.DecideSuit(g))
				continue
			}
			if pending := g.PendingEightSuit; pending != nil {
				for _, card := range g.current().Hand {
					want := card.Suit == *pending || card.Rank == Eight
					if g.canPlay(card) != want {
						t.Fatalf("партия %d, ход %d: canPlay(%v) при требовании покрыть масть %v — получено %v, ожидалось %v",
							game, turns, card, *pending, g.canPlay(card), want)
					}
				}
			}
			if card, ok := bot.DecideAction(g); ok {
				if err := g.PlayCard(card); err != nil {
					t.Fatalf("партия %d, ход %d: неожиданная ошибка розыгрыша %v: %v", game, turns, card, err)
				}
			} else {
				if err := g.Draw(); err != nil {
					t.Fatalf("партия %d, ход %d: неожиданная ошибка добора: %v", game, turns, err)
				}
			}
		}
	}
}
