package main

import (
	"fmt"
	"testing"
)

func TestPokerModelBotsGetRandomDifficulties(t *testing.T) {
	m := NewPokerModel(4)
	if len(m.bots) != 3 {
		t.Fatalf("ожидалось 3 бота (4 игрока минус человек), получено %d", len(m.bots))
	}
	for seat, bot := range m.bots {
		if bot.Difficulty < PokerDifficultyEasy || bot.Difficulty > PokerDifficultyHard {
			t.Errorf("бот на месте %d получил недопустимый уровень сложности %v", seat, bot.Difficulty)
		}
	}
}

func TestPokerModelHumanIsSeatZero(t *testing.T) {
	m := NewPokerModel(3)
	if m.humanSeat != 0 {
		t.Errorf("человек должен всегда занимать место 0, получено %d", m.humanSeat)
	}
	if _, isBot := m.bots[m.humanSeat]; isBot {
		t.Errorf("на месте человека не должно быть бота")
	}
}

func TestPokerModelFoldViaKey(t *testing.T) {
	m := NewPokerModel(3)
	m.game.CurrentPlayerIdx = m.humanSeat
	next, _ := m.Update(key("1"))
	m = next.(PokerModel)
	if !m.game.Players[m.humanSeat].Folded {
		t.Errorf("после выбора действия 'сбросить' игрок должен был сбросить карты")
	}
}

func TestPokerModelRejectsActionWhenNotHumanTurn(t *testing.T) {
	m := NewPokerModel(3)
	m.game.CurrentPlayerIdx = (m.humanSeat + 1) % 3
	next, _ := m.Update(key("1"))
	m = next.(PokerModel)
	if m.game.Players[m.humanSeat].Folded {
		t.Errorf("действие не должно было приняться — сейчас не ход человека")
	}
}

func TestPokerModelBetAmountSubmode(t *testing.T) {
	m := NewPokerModel(2)
	m.game.CurrentPlayerIdx = m.humanSeat
	actions := m.pokerHumanActions()
	betIdx := -1
	for i, a := range actions {
		if a == "bet" {
			betIdx = i
		}
	}
	if betIdx < 0 {
		t.Fatalf("ожидалось действие 'bet' среди доступных: %v", actions)
	}

	next, _ := m.Update(key(fmt.Sprintf("%d", betIdx+1)))
	m = next.(PokerModel)
	if !m.choosingBetAmount {
		t.Fatalf("ожидался переход в подрежим выбора суммы ставки")
	}
	before := m.betAmount

	next, _ = m.Update(key("up"))
	m = next.(PokerModel)
	if m.betAmount != before+m.game.BigBlind {
		t.Errorf("ожидалось увеличение ставки на большой блайнд, было %d, стало %d", before, m.betAmount)
	}

	next, _ = m.Update(key("enter"))
	m = next.(PokerModel)
	if m.choosingBetAmount {
		t.Errorf("подрежим должен был закрыться после подтверждения")
	}
	human := m.game.Players[m.humanSeat]
	if human.CurrentBet != before+m.game.BigBlind {
		t.Errorf("ставка должна была реально примениться, ожидалось %d, получено %d", before+m.game.BigBlind, human.CurrentBet)
	}
}

func TestPokerModelBetAmountCancelDoesNotApply(t *testing.T) {
	m := NewPokerModel(2)
	m.game.CurrentPlayerIdx = m.humanSeat
	betBefore := m.game.Players[m.humanSeat].CurrentBet

	actions := m.pokerHumanActions()
	betIdx := 0
	for i, a := range actions {
		if a == "bet" {
			betIdx = i
		}
	}
	next, _ := m.Update(key(fmt.Sprintf("%d", betIdx+1)))
	m = next.(PokerModel)
	next, _ = m.Update(key("esc"))
	m = next.(PokerModel)
	if m.choosingBetAmount {
		t.Errorf("esc должен был закрыть подрежим без применения ставки")
	}
	if m.game.Players[m.humanSeat].CurrentBet != betBefore {
		t.Errorf("отменённая ставка не должна была примениться")
	}
}

func TestPokerModelBotPlaysWhenNotHumanTurn(t *testing.T) {
	m := NewPokerModel(3)
	for m.game.CurrentPlayerIdx == m.humanSeat {
		m.game.CurrentPlayerIdx = (m.game.CurrentPlayerIdx + 1) % 3
	}
	next, _ := m.Update(botMoveMsg{})
	m = next.(PokerModel)
	if m.isError {
		t.Errorf("неожиданная ошибка хода бота: %s", m.message)
	}
}

func TestPokerModelIgnoresBotMoveMsgOnHumanTurn(t *testing.T) {
	m := NewPokerModel(3)
	m.game.CurrentPlayerIdx = m.humanSeat
	before := *m.game
	next, _ := m.Update(botMoveMsg{})
	m = next.(PokerModel)
	if m.game.CurrentPlayerIdx != before.CurrentPlayerIdx {
		t.Errorf("botMoveMsg не должно было ничего менять, пока сейчас ход человека")
	}
}

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

func TestPokerModelNextHandViaKey(t *testing.T) {
	m := NewPokerModel(2)
	m.game.Players[0].HoleCards = [2]Card{c(Ace, Spades), c(Ace, Hearts)}
	m.game.Players[1].HoleCards = [2]Card{c(Two, Clubs), c(Seven, Diamonds)}
	m.game.Board = []Card{c(Ace, Clubs), c(King, Spades), c(Queen, Hearts), c(Four, Diamonds), c(Nine, Clubs)}
	m.game.Players[0].TotalContributed = 100
	m.game.Players[1].TotalContributed = 100
	m.game.resolveShowdown()

	next, _ := m.Update(key("n"))
	m = next.(PokerModel)
	if m.game.Phase == PokerPhaseShowdown {
		t.Errorf("после 'n' должна была начаться новая раздача")
	}
}

func TestPokerModelFullTournamentAutoPlaythrough(t *testing.T) {
	m := NewPokerModel(4)
	humanBot := PokerBot{Difficulty: PokerDifficultyMedium}

	steps := 0
	for m.game.Phase != PokerPhaseTournamentOver && steps < 50000 {
		steps++
		g := m.game
		if g.Phase == PokerPhaseShowdown {
			next, _ := m.Update(key("n"))
			m = next.(PokerModel)
			continue
		}
		if g.CurrentPlayerIdx != m.humanSeat {
			next, _ := m.Update(botMoveMsg{})
			m = next.(PokerModel)
			continue
		}
		action := humanBot.DecideAction(g, m.humanSeat, m.rng)
		actions := m.pokerHumanActions()
		labelToKey := map[string]int{}
		for i, a := range actions {
			labelToKey[a] = i + 1
		}
		var keyStr string
		switch action.Kind {
		case "fold":
			keyStr = fmt.Sprintf("%d", labelToKey["fold"])
		case "check":
			if idx, ok := labelToKey["check"]; ok {
				keyStr = fmt.Sprintf("%d", idx)
			} else {
				keyStr = fmt.Sprintf("%d", labelToKey["call"])
			}
		case "call":
			keyStr = fmt.Sprintf("%d", labelToKey["call"])
		case "bet":
			keyStr = fmt.Sprintf("%d", labelToKey["bet"])
		}
		next, _ := m.Update(key(keyStr))
		m = next.(PokerModel)
		if m.choosingBetAmount {
			next, _ = m.Update(key("enter"))
			m = next.(PokerModel)
		}
		if m.isError {
			t.Fatalf("неожиданная ошибка при автопрогоне: %s", m.message)
		}
	}
	if m.game.Phase != PokerPhaseTournamentOver {
		t.Fatalf("турнир не завершился за %d шагов", steps)
	}
}
