package main

import (
	"strings"
	"testing"
)

func TestBlackjackModelDealsCorrectPlayerCount(t *testing.T) {
	for n := 1; n <= 6; n++ {
		m := NewBlackjackModel(n, 100, 10)
		if len(m.game.Players) != n {
			t.Errorf("ожидалось %d игроков, получено %d", n, len(m.game.Players))
		}
	}
}

func TestBlackjackModelClampsPlayerCount(t *testing.T) {
	m := NewBlackjackModel(0, 100, 10)
	if len(m.game.Players) != 1 {
		t.Errorf("ожидалось ограничение снизу до 1 игрока, получено %d", len(m.game.Players))
	}
	m = NewBlackjackModel(10, 100, 10)
	if len(m.game.Players) != 6 {
		t.Errorf("ожидалось ограничение сверху до 6 игроков, получено %d", len(m.game.Players))
	}
}

func TestBlackjackModelHitViaKeys(t *testing.T) {
	m := NewBlackjackModel(1, 100, 10)
	resetForManualBout(m.game)
	m.game.Players[0].Hand = []Card{c(King, Clubs), c(Queen, Hearts)}
	m.game.CurrentPlayerIdx = 0
	m.game.Phase = BJPhasePlayerTurn
	m.game.Deck = &Deck{cards: []Card{c(Five, Diamonds)}}

	next, _ := m.Update(key("h"))
	m = next.(BlackjackModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка: %s", m.message)
	}
	if m.game.Players[0].Status != BJStatusBusted {
		t.Fatalf("ожидался перебор, статус: %v", m.game.Players[0].Status)
	}
}

func TestBlackjackModelStandViaKeys(t *testing.T) {
	m := NewBlackjackModel(1, 100, 10)
	resetForManualBout(m.game)
	m.game.CurrentPlayerIdx = 0
	m.game.Phase = BJPhasePlayerTurn

	next, _ := m.Update(key("s"))
	m = next.(BlackjackModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка: %s", m.message)
	}
	if m.game.Phase != BJPhaseRoundOver {
		t.Fatalf("с одним игроком после stand раунд должен был сразу завершиться")
	}
}

func TestBlackjackModelDoubleViaKeys(t *testing.T) {
	m := NewBlackjackModel(1, 100, 10)
	resetForManualBout(m.game)
	m.game.Players[0].Hand = []Card{c(Five, Clubs), c(Six, Hearts)}
	m.game.CurrentPlayerIdx = 0
	m.game.Phase = BJPhasePlayerTurn
	m.game.Deck = &Deck{cards: []Card{c(King, Diamonds)}}

	next, _ := m.Update(key("d"))
	m = next.(BlackjackModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка: %s", m.message)
	}
	if m.game.Players[0].Bet != 20 {
		t.Fatalf("ставка должна была удвоиться, получено %d", m.game.Players[0].Bet)
	}
}

func TestBlackjackModelQReturnsToMenu(t *testing.T) {
	m := NewBlackjackModel(2, 100, 10)
	next, _ := m.Update(key("q"))
	m = next.(BlackjackModel)
	if !m.backToMenu {
		t.Fatalf("ожидался флаг backToMenu после 'q'")
	}
	if m.quitting {
		t.Fatalf("'q' не должен закрывать всё приложение")
	}
}

func TestBlackjackModelNewRoundCarriesBalance(t *testing.T) {
	m := NewBlackjackModel(1, 100, 10)
	resetForManualBout(m.game)
	m.game.CurrentPlayerIdx = 0
	m.game.Phase = BJPhasePlayerTurn

	next, _ := m.Update(key("s"))
	m = next.(BlackjackModel)
	if m.game.Phase != BJPhaseRoundOver {
		t.Fatalf("раунд должен был завершиться")
	}
	balanceAfterRound := m.game.Players[0].Balance

	next, _ = m.Update(key("n"))
	m = next.(BlackjackModel)
	// с одним игроком новый раунд может мгновенно разрешиться сам,
	// если случайно выпадет блэкджек с раздачи — это ожидаемое
	// поведение, а не баг, поэтому проверяем именно перенесённый
	// капитал (preRoundBalance), а не итоговый Balance
	if m.game.preRoundBalance[0] != balanceAfterRound {
		t.Fatalf("капитал должен был перенестись в новый раунд: ожидалось %d, получено %d",
			balanceAfterRound, m.game.preRoundBalance[0])
	}
}

// TestBlackjackModelFullAutoPlaythrough прогоняет TUI-модель от
// начала до конца для человека, отыгрывающего по той же логике, что
// и бот, но через реальные нажатия клавиш — для нескольких
// вариантов числа игроков и нескольких раундов подряд.
func TestBlackjackModelFullAutoPlaythrough(t *testing.T) {
	bot := BlackjackBot{}
	for n := 1; n <= 6; n++ {
		m := NewBlackjackModel(n, 1000, 10)
		for round := 0; round < 20; round++ {
			const maxSteps = 200
			steps := 0
			for m.game.Phase != BJPhaseRoundOver && steps < maxSteps {
				steps++
				if m.game.CurrentPlayerIdx != m.humanIdx {
					next, _ := m.Update(botMoveMsg{})
					m = next.(BlackjackModel)
					continue
				}
				action := bot.DecideAction(m.game)
				var k string
				switch action {
				case "double":
					k = "d"
				case "hit":
					k = "h"
				default:
					k = "s"
				}
				next, _ := m.Update(key(k))
				m = next.(BlackjackModel)
			}
			if steps >= maxSteps {
				t.Fatalf("игроков %d, раунд %d: раунд через TUI не завершился за %d шагов", n, round, maxSteps)
			}
			if m.game.Result == nil {
				t.Fatalf("игроков %d, раунд %d: раунд завершился без результата", n, round)
			}
			next, _ := m.Update(key("n"))
			m = next.(BlackjackModel)
		}
	}
}

func TestBlackjackModelShowsMessageWhenBetReduced(t *testing.T) {
	m := NewBlackjackModel(1, 100, 10)
	for m.game.Phase == BJPhasePlayerTurn {
		if err := m.game.Stand(); err != nil {
			t.Fatalf("неожиданная ошибка: %v", err)
		}
	}
	m.game.Players[m.humanIdx].Balance = 4 // меньше полной ставки 10, но больше нуля

	next, _ := m.Update(key("n"))
	m = next.(BlackjackModel)
	if !m.game.RoundBetReduced[m.humanIdx] {
		t.Fatalf("ожидался флаг урезанной ставки")
	}
	if m.game.Players[m.humanIdx].Bet != 4 {
		t.Errorf("ставка должна была урезаться до 4, получено %d", m.game.Players[m.humanIdx].Bet)
	}
	if m.message == "" {
		t.Errorf("ожидалось информационное сообщение об урезанной ставке")
	}
	if m.isError {
		t.Errorf("сообщение об урезанной ставке не должно быть ошибкой")
	}
}

func TestBlackjackModelBankruptBlocksNextRoundAndShowsMessage(t *testing.T) {
	m := NewBlackjackModel(1, 100, 10)
	for m.game.Phase == BJPhasePlayerTurn {
		if err := m.game.Stand(); err != nil {
			t.Fatalf("неожиданная ошибка: %v", err)
		}
	}
	m.game.Players[m.humanIdx].Balance = 0
	handsBefore := len(m.game.Players[m.humanIdx].Hand)

	if !m.humanIsBankrupt() {
		t.Fatalf("ожидалось, что humanIsBankrupt() вернёт true при нулевом балансе")
	}
	view := m.View()
	if !strings.Contains(view, T("blackjack.bankrupt")) {
		t.Errorf("экран должен был показать сообщение о банкротстве, получено:\n%s", view)
	}

	next, _ := m.Update(key("n"))
	m = next.(BlackjackModel)
	if m.game.Phase != BJPhaseRoundOver {
		t.Errorf("нажатие 'n' при банкротстве не должно было начинать новый раунд")
	}
	if len(m.game.Players[m.humanIdx].Hand) != handsBefore {
		t.Errorf("рука не должна была измениться — новый раунд не должен был начаться")
	}
}
