package main

import (
	"math/rand"
	"testing"
)

func playNardyFullGame(t *testing.T, g *NardyGameState, botWhite, botBlack NardyBot, rnd *rand.Rand, maxTurns int) {
	t.Helper()
	for turn := 0; turn < maxTurns && g.Phase != NardyPhaseOver; turn++ {
		var bot NardyBot
		if g.CurrentPlayer == NardyWhite {
			bot = botWhite
		} else {
			bot = botBlack
		}
		if err := bot.PlayFullTurn(g, rnd); err != nil {
			t.Fatalf("ход %d: неожиданная ошибка бота: %v", turn, err)
		}
	}
	if g.Phase != NardyPhaseOver {
		t.Fatalf("партия не завершилась за %d ходов — похоже на зависание. BorneOff=%v Bar=%v", maxTurns, g.BorneOff, g.Bar)
	}
}

func TestNardyPipCountBasic(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[0] = NardyPoint{Owner: NardyWhite, Count: 1} // rel=0 -> 1 очко
	g.Board[5] = NardyPoint{Owner: NardyWhite, Count: 2} // rel=5 -> 6 очков каждая = 12
	if got := nardyPipCount(g, NardyWhite); got != 13 {
		t.Errorf("ожидался pip count 13 (1+6+6), получено %d", got)
	}
}

func TestNardyPipCountIncludesBar(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Bar[NardyWhite] = 1
	if got := nardyPipCount(g, NardyWhite); got != 25 {
		t.Errorf("шашка на баре должна считаться как 25 очков, получено %d", got)
	}
}

func TestMediumBotPrefersHitWhenEquallyExposedOtherwise(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	// единственная шашка белых способна пойти либо на открытую
	// пустую точку (тоже остаётся одиноким блотом), либо на точку с
	// блотом чёрных (тоже остаётся блотом, но со сбитой шашкой
	// соперника) — оба варианта одинаково уязвимы сами по себе,
	// значит удар должен явно перевешивать по выгоде
	g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.Board[5] = NardyPoint{Owner: NardyBlack, Count: 1} // блот чёрных, досягаем костью 5
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{5}

	bot := NardyBot{Difficulty: NardyDifficultyMedium}
	seq := bot.DecideSequence(g, rnd)
	if len(seq) != 1 || seq[0].From != 10 || seq[0].Die != 5 {
		t.Errorf("при прочих равных средний уровень должен был предпочесть удар, получено %+v", seq)
	}
}

func TestNardyEvaluateRewardsHittingAllElseEqual(t *testing.T) {
	setup := func() *NardyGameState {
		g := NewNardyGame(NardyShort, NardyWhite)
		g.Board = [24]NardyPoint{}
		return g
	}
	// сценарий А: белая шашка одна на точке 5 (блот) — просто дошла туда без удара
	gNoHit := setup()
	gNoHit.Board[5] = NardyPoint{Owner: NardyWhite, Count: 1}
	scoreNoHit := nardyEvaluate(gNoHit, NardyWhite)

	// сценарий Б: та же позиция, но на баре у чёрных сидит сбитая
	// шашка (как будто именно она стояла на точке 5 и была сбита) —
	// изолированно проверяем эффект наличия чужой шашки на баре
	gHit := setup()
	gHit.Board[5] = NardyPoint{Owner: NardyWhite, Count: 1}
	gHit.Bar[NardyBlack] = 1
	scoreHit := nardyEvaluate(gHit, NardyWhite)

	if scoreHit <= scoreNoHit {
		t.Errorf("наличие шашки соперника на баре должно было увеличивать оценку позиции для белых: без удара=%v, с шашкой соперника на баре=%v", scoreNoHit, scoreHit)
	}
}

func TestHardBotSequenceUsesMaximalDice(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	g := NewNardyGame(NardyShort, NardyWhite)
	bot := NardyBot{Difficulty: NardyDifficultyHard}
	g.Roll(6, 4)
	needed := g.nardyMaxDiceUsableFrom(g.DiceLeft)

	seq := bot.DecideSequence(g, rnd)
	if len(seq) != needed {
		t.Errorf("последовательность жёсткого уровня должна была использовать максимум костей (%d), получено %d ходов: %+v", needed, len(seq), seq)
	}
}

func TestEasyBotSequenceOnlyLegalMoves(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	g := NewNardyGame(NardyLong, NardyWhite)
	bot := NardyBot{Difficulty: NardyDifficultyEasy}
	g.Roll(3, 5)

	seq := bot.DecideSequence(g, rnd)
	state := g.cloneForSearch()
	for _, mv := range seq {
		legal := state.LegalMovesNow()
		found := false
		for _, l := range legal {
			if l == mv {
				found = true
			}
		}
		if !found {
			t.Fatalf("ход %+v не входит в число легальных на этом шаге: %+v", mv, legal)
		}
		state.applyMoveRaw(mv)
		state.removeDieOnce(mv.Die)
	}
}

func TestPlayFullTurnRollsWhenNeeded(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	g := NewNardyGame(NardyShort, NardyWhite)
	bot := NardyBot{Difficulty: NardyDifficultyEasy}
	if g.Phase != NardyPhaseRoll {
		t.Fatalf("новая партия должна начинаться в фазе броска")
	}
	if err := bot.PlayFullTurn(g, rnd); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.CurrentPlayer != NardyBlack {
		t.Errorf("после полного хода бота очередь должна была перейти к чёрным")
	}
}

func TestNardyBotFullGamesShortEasyVsEasy(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	for i := 0; i < 5; i++ {
		g := NewNardyGame(NardyShort, NardyWhite)
		bot := NardyBot{Difficulty: NardyDifficultyEasy}
		playNardyFullGame(t, g, bot, bot, rnd, 2000)
	}
}

func TestNardyBotFullGamesShortMediumVsMedium(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	for i := 0; i < 5; i++ {
		g := NewNardyGame(NardyShort, NardyWhite)
		bot := NardyBot{Difficulty: NardyDifficultyMedium}
		playNardyFullGame(t, g, bot, bot, rnd, 2000)
	}
}

func TestNardyBotFullGamesShortHardVsHard(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	for i := 0; i < 3; i++ {
		g := NewNardyGame(NardyShort, NardyWhite)
		bot := NardyBot{Difficulty: NardyDifficultyHard}
		playNardyFullGame(t, g, bot, bot, rnd, 2000)
	}
}

func TestNardyBotFullGamesLongEasyVsEasy(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	for i := 0; i < 5; i++ {
		g := NewNardyGame(NardyLong, NardyWhite)
		bot := NardyBot{Difficulty: NardyDifficultyEasy}
		playNardyFullGame(t, g, bot, bot, rnd, 2000)
	}
}

func TestNardyBotFullGamesLongMediumVsMedium(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	for i := 0; i < 5; i++ {
		g := NewNardyGame(NardyLong, NardyWhite)
		bot := NardyBot{Difficulty: NardyDifficultyMedium}
		playNardyFullGame(t, g, bot, bot, rnd, 2000)
	}
}

func TestNardyBotFullGamesLongHardVsHard(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	for i := 0; i < 3; i++ {
		g := NewNardyGame(NardyLong, NardyWhite)
		bot := NardyBot{Difficulty: NardyDifficultyHard}
		playNardyFullGame(t, g, bot, bot, rnd, 2000)
	}
}

func TestNardyBotFullGamesMixedDifficulties(t *testing.T) {
	rnd := rand.New(rand.NewSource(1))
	combos := []struct {
		white, black NardyDifficulty
	}{
		{NardyDifficultyEasy, NardyDifficultyMedium},
		{NardyDifficultyMedium, NardyDifficultyHard},
		{NardyDifficultyEasy, NardyDifficultyHard},
	}
	for _, variant := range []NardyVariant{NardyShort, NardyLong} {
		for _, combo := range combos {
			g := NewNardyGame(variant, NardyWhite)
			playNardyFullGame(t, g, NardyBot{Difficulty: combo.white}, NardyBot{Difficulty: combo.black}, rnd, 2000)
		}
	}
}
