package main

import (
	"fmt"
	"testing"
)

func TestNardyModelRollViaKey(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	m.game = NewNardyGame(NardyShort, NardyWhite)
	m.humanColor = NardyWhite

	next, _ := m.Update(key("r"))
	m = next.(NardyModel)
	if m.game.Phase != NardyPhaseMove && m.game.Phase != NardyPhaseRoll {
		t.Fatalf("после броска фаза должна быть Move (или снова Roll, если ходов не нашлось), получено %v", m.game.Phase)
	}
	if m.game.Dice == [2]int{} {
		t.Errorf("после броска кости не должны оставаться нулевыми")
	}
}

func TestNardyModelApplyMoveViaDigitKey(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	m.game = NewNardyGame(NardyShort, NardyWhite)
	m.humanColor = NardyWhite
	m.game.Roll(3, 4)
	if m.game.CurrentPlayer != NardyWhite {
		t.Fatalf("тест предполагает, что сейчас ход человека")
	}
	groupsBefore := nardySourceGroups(m.game.LegalMovesNow())
	if len(groupsBefore) == 0 {
		t.Fatalf("ожидались легальные ходы после броска 3,4 в начальной позиции")
	}

	next, _ := m.Update(key("1"))
	m = next.(NardyModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка: %s", m.message)
	}
	// после выбора источника — либо ход сразу применился (если у
	// источника был единственный вариант), либо открылся подрежим
	// выбора кости
	if !m.choosingDie && len(m.game.DiceLeft) != 1 {
		t.Errorf("ожидалось либо применение хода (осталась 1 кость), либо подрежим выбора кости, получено DiceLeft=%v choosingDie=%v", m.game.DiceLeft, m.choosingDie)
	}
}

func TestNardyModelTwoStepDieSelection(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	m.game = NewNardyGame(NardyShort, NardyWhite)
	m.humanColor = NardyWhite
	m.game.Board = [24]NardyPoint{}
	// одна шашка, с которой можно сходить и костью 3, и костью 5 —
	// источник один, но два варианта кости
	m.game.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
	m.game.CurrentPlayer = NardyWhite
	m.game.Phase = NardyPhaseMove
	m.game.DiceLeft = []int{3, 5}

	groups := nardySourceGroups(m.game.LegalMovesNow())
	if len(groups) != 1 || len(groups[0]) != 2 {
		t.Fatalf("тест предполагает один источник с двумя вариантами кости, получено %+v", groups)
	}

	next, _ := m.Update(key("1"))
	m = next.(NardyModel)
	if !m.choosingDie {
		t.Fatalf("ожидался подрежим выбора кости, раз у источника два варианта")
	}
	if len(m.pendingSourceMoves) != 2 {
		t.Errorf("ожидалось 2 ожидающих варианта кости, получено %d", len(m.pendingSourceMoves))
	}

	next, _ = m.Update(key("1"))
	m = next.(NardyModel)
	if m.choosingDie {
		t.Errorf("подрежим выбора кости должен был закрыться после выбора")
	}
	if len(m.game.DiceLeft) != 1 {
		t.Errorf("после выбора кости должна была остаться одна неиспользованная, получено %v", m.game.DiceLeft)
	}
}

func TestNardyModelRejectsMoveKeyDuringRollPhase(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	m.game = NewNardyGame(NardyShort, NardyWhite)
	m.humanColor = NardyWhite

	next, _ := m.Update(key("1"))
	m = next.(NardyModel)
	if m.game.Phase != NardyPhaseRoll {
		t.Errorf("нажатие цифры в фазе броска не должно было ничего изменить")
	}
}

func TestNardyModelRejectsRollKeyDuringMovePhase(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	m.game = NewNardyGame(NardyShort, NardyWhite)
	m.humanColor = NardyWhite
	m.game.Roll(3, 4)
	diceBefore := m.game.Dice

	next, _ := m.Update(key("r"))
	m = next.(NardyModel)
	if m.game.Dice != diceBefore {
		t.Errorf("повторный бросок в фазе хода не должен был происходить")
	}
}

func TestNardyModelBotPlaysWhenNotHumanTurn(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	m.game = NewNardyGame(NardyShort, NardyBlack)
	m.humanColor = NardyWhite

	next, _ := m.Update(botMoveMsg{})
	m = next.(NardyModel)
	if m.game.CurrentPlayer != NardyWhite {
		t.Errorf("после полного хода бота очередь должна была перейти к человеку (белым)")
	}
}

func TestNardyModelIgnoresBotMoveMsgOnHumanTurn(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	m.game = NewNardyGame(NardyShort, NardyWhite)
	m.humanColor = NardyWhite

	next, _ := m.Update(botMoveMsg{})
	m = next.(NardyModel)
	if m.game.Phase != NardyPhaseRoll {
		t.Errorf("botMoveMsg не должно было ничего менять, пока сейчас ход человека")
	}
}

func TestNardyModelQReturnsToMenuNotQuitsApp(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	next, _ := m.Update(key("q"))
	m = next.(NardyModel)
	if !m.backToMenu {
		t.Errorf("ожидался флаг backToMenu после 'q'")
	}
	if m.quitting {
		t.Errorf("'q' не должно завершать приложение целиком")
	}
}

func TestNardyModelActionsIgnoredAfterGameOver(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	m.game = NewNardyGame(NardyShort, NardyWhite)
	m.humanColor = NardyWhite
	m.game.Phase = NardyPhaseOver
	m.game.Result = &NardyResult{Winner: NardyWhite}

	next, _ := m.Update(key("1"))
	m = next.(NardyModel)
	if m.game.BorneOff[NardyWhite] != 0 {
		t.Errorf("действия после завершения партии не должны приниматься")
	}
}

func TestNardyMoveLabelFormatsCorrectly(t *testing.T) {
	label := nardyMoveLabel(NardyMove{From: 10, Die: 5})
	want := Tf("nardy.move_label", "11", "6", 5)
	if label != want {
		t.Errorf("ожидалась метка %q, получено %q", want, label)
	}
}

func TestNardyMoveLabelBearOff(t *testing.T) {
	label := nardyMoveLabel(NardyMove{From: 3, Die: 4, BearOff: true})
	want := Tf("nardy.move_label", "4", "снятие", 4)
	if label != want {
		t.Errorf("ожидалась метка %q, получено %q", want, label)
	}
}

func TestNardyMoveLabelFromBar(t *testing.T) {
	// вход костью 3: entryRel = 24-3 = 21, традиционный пункт = 22
	label := nardyMoveLabel(NardyMove{FromBar: true, Die: 3})
	want := Tf("nardy.move_label", "с бара", "22", 3)
	if label != want {
		t.Errorf("ожидалась метка %q, получено %q", want, label)
	}
}

func TestNardyModelFullAutoPlaythrough(t *testing.T) {
	for _, variant := range []NardyVariant{NardyShort, NardyLong} {
		m := NewNardyModel(variant, NardyDifficultyMedium, ColorChoiceA)
		humanBot := NardyBot{Difficulty: NardyDifficultyMedium}

		steps := 0
		for m.game.Phase != NardyPhaseOver && steps < 20000 {
			steps++
			if m.game.CurrentPlayer != m.humanColor {
				next, _ := m.Update(botMoveMsg{})
				m = next.(NardyModel)
				continue
			}
			switch {
			case m.game.Phase == NardyPhaseRoll:
				next, _ := m.Update(key("r"))
				m = next.(NardyModel)
			case m.choosingDie:
				seq := humanBot.DecideSequence(m.game, m.rng)
				if len(seq) == 0 {
					t.Fatalf("бот-имитатор не выбрал ход в подрежиме выбора кости")
				}
				idx := -1
				for i, mv := range m.pendingSourceMoves {
					if mv == seq[0] {
						idx = i
						break
					}
				}
				if idx < 0 {
					// выбранный ботом ход относится к другому источнику —
					// имитатор человека просто берёт первый доступный вариант
					// кости у уже выбранного источника (эквивалентная по
					// корректности проверка сквозного прохождения партии)
					idx = 0
				}
				next, _ := m.Update(key(fmt.Sprintf("%d", idx+1)))
				m = next.(NardyModel)
				if m.isError {
					t.Fatalf("неожиданная ошибка при выборе кости: %s", m.message)
				}
			case m.game.Phase == NardyPhaseMove:
				groups := nardySourceGroups(m.game.LegalMovesNow())
				if len(groups) == 0 {
					t.Fatalf("LegalMovesNow пуст в фазе Move для хода человека — не должно происходить")
				}
				if len(groups) > 9 {
					t.Fatalf("число различных источников хода превысило 9: %d", len(groups))
				}
				seq := humanBot.DecideSequence(m.game, m.rng)
				if len(seq) == 0 {
					t.Fatalf("бот-имитатор человека не выбрал ни одного хода")
				}
				idx := -1
				for i, group := range groups {
					if group[0].FromBar == seq[0].FromBar && group[0].From == seq[0].From {
						idx = i
						break
					}
				}
				if idx < 0 {
					idx = 0 // любой источник корректен для проверки сквозного прохождения
				}
				next, _ := m.Update(key(fmt.Sprintf("%d", idx+1)))
				m = next.(NardyModel)
				if m.isError {
					t.Fatalf("неожиданная ошибка при выборе источника: %s", m.message)
				}
			}
		}
		if m.game.Phase != NardyPhaseOver {
			t.Fatalf("партия (вариант %v) не завершилась за %d шагов", variant, steps)
		}
	}
}

func TestNewNardyModelRespectsColorChoiceWhite(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceA)
	if m.humanColor != NardyWhite {
		t.Errorf("при выборе ColorChoiceA человек должен был играть белыми, получено %v", m.humanColor)
	}
}

func TestNewNardyModelRespectsColorChoiceBlack(t *testing.T) {
	m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceB)
	if m.humanColor != NardyBlack {
		t.Errorf("при выборе ColorChoiceB человек должен был играть чёрными, получено %v", m.humanColor)
	}
}

func TestNewNardyModelRandomColorGivesValidColor(t *testing.T) {
	seenWhite, seenBlack := false, false
	for i := 0; i < 50 && !(seenWhite && seenBlack); i++ {
		m := NewNardyModel(NardyShort, NardyDifficultyEasy, ColorChoiceRandom)
		if m.humanColor == NardyWhite {
			seenWhite = true
		} else {
			seenBlack = true
		}
	}
	if !seenWhite || !seenBlack {
		t.Errorf("случайный выбор цвета должен был давать оба варианта за 50 попыток: белые=%v чёрные=%v", seenWhite, seenBlack)
	}
}
