package main

import "testing"

func TestStalemateProtectionTerminatesGame(t *testing.T) {
	g := NewDurakGame([]string{"A", "B"}, -1)
	g.Talon = nil
	g.TrumpSuit = Spades
	g.AttackerIdx = 0
	g.DefenderIdx = 1
	// у обоих всегда есть по карте, а защищающийся никогда не может
	// отбиться (у него всегда некозырная карта младше атакующей) —
	// имитируем принудительное "взятие" много раз подряд напрямую
	for i := 0; i < maxTakesWithoutBito+5 && g.Phase != DurakPhaseGameOver; i++ {
		g.Players[0].Hand = []Card{c(Eight, Clubs), c(Nine, Diamonds)} // запасная карта, чтобы не выбыть
		g.Players[1].Hand = []Card{c(Six, Hearts)}
		g.attackCap = g.currentAttackCap()
		g.Phase = DurakPhaseAttack
		if err := g.ThrowIn(c(Eight, Clubs)); err != nil {
			t.Fatalf("шаг %d: неожиданная ошибка подкидывания: %v", i, err)
		}
		if err := g.Take(); err != nil {
			t.Fatalf("шаг %d: неожиданная ошибка взятия: %v", i, err)
		}
		if g.Phase != DurakPhaseGameOver {
			// перед следующей итерацией восстанавливаем роли и стол
			// вручную — в реальной игре это происходит по правилам
			g.AttackerIdx, g.DefenderIdx = 0, 1
		}
	}
	if g.Phase != DurakPhaseGameOver {
		t.Fatalf("партия должна была принудительно завершиться из-за защиты от зацикливания")
	}
	if g.Result == nil || !g.Result.Stalemate {
		t.Fatalf("ожидался результат Stalemate, получено: %+v", g.Result)
	}
}

// TestDurakBotFullGames прогоняет много полных партий силами
// DurakBot для разного числа игроков (2-6) и проверяет, что игра
// всегда корректно завершается: без паники, без зависания, и с
// валидным результатом (ровно один проигравший либо ничья).
func TestDurakBotFullGames(t *testing.T) {
	bot := DurakBot{}

	for numPlayers := 2; numPlayers <= 6; numPlayers++ {
		for game := 0; game < 30; game++ {
			names := make([]string, numPlayers)
			for i := range names {
				names[i] = "Bot"
			}
			g := NewDurakGame(names, -1)

			const maxSteps = 10000
			steps := 0
			for ; steps < maxSteps && g.Phase != DurakPhaseGameOver; steps++ {
				if err := bot.PlayFullTurn(g); err != nil {
					t.Fatalf("игроков %d, игра %d, шаг %d: ошибка бота: %v",
						numPlayers, game, steps, err)
				}
			}

			if steps >= maxSteps {
				t.Fatalf("игроков %d, игра %d: партия не завершилась за %d шагов (возможное зависание)",
					numPlayers, game, maxSteps)
			}
			if g.Result == nil {
				t.Fatalf("игроков %d, игра %d: партия завершилась без результата", numPlayers, game)
			}
			if !g.Result.NoLoser && !g.Result.Stalemate {
				if g.Result.Loser < 0 || g.Result.Loser >= numPlayers {
					t.Fatalf("игроков %d, игра %d: некорректный индекс проигравшего %d",
						numPlayers, game, g.Result.Loser)
				}
			}

			// суммарно карты не должны теряться и не должны
			// дублироваться: в талоне + на руках должно остаться
			// ровно то, что не ушло в "бито" (отбой). Проверяем
			// более простой инвариант — отсутствие дубликатов среди
			// всех карт, которые сейчас на руках и в талоне.
			seen := map[Card]bool{}
			for _, p := range g.Players {
				for _, c := range p.Hand {
					if seen[c] {
						t.Fatalf("игроков %d, игра %d: карта %v встретилась дважды", numPlayers, game, c)
					}
					seen[c] = true
				}
			}
			for _, c := range g.Talon {
				if seen[c] {
					t.Fatalf("игроков %d, игра %d: карта %v встретилась дважды (в талоне и на руке)", numPlayers, game, c)
				}
				seen[c] = true
			}
		}
	}
}
