package main

import "testing"

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

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

func TestDurakModelAutoPassesHumanWithNothingToThrow(t *testing.T) {
	m := NewDurakModel(3)
	m.humanIdx = 2 // человек — третий игрок (индекс 2), сам атакующий
	setupBout(m.game, 2, 1, Spades, map[int][]Card{
		0: {dc(Ten, Diamonds)},                    // сторонний бот, роли не играет
		1: {dc(Seven, Clubs), dc(Jack, Diamonds)}, // защищающийся
		2: {dc(Six, Clubs), dc(Nine, Hearts)},     // атакует шестёркой, девятка не подходит по рангу
	})
	if err := m.game.ThrowIn(dc(Six, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := m.game.Defend(dc(Seven, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if m.game.Phase != DurakPhaseThrowIn || m.game.CurrentThrower() != m.humanIdx {
		t.Fatalf("тест предполагает, что сейчас очередь человека подкидывать (атакующий ходит первым в очереди)")
	}

	m.maybeScheduleBot()

	if m.game.CurrentThrower() == m.humanIdx && m.game.Phase == DurakPhaseThrowIn {
		t.Errorf("человека без подходящих карт должно было автоматически пропустить (пас), а не оставить ждать ввода")
	}
}

func TestDurakModelDoesNotAutoPassHumanWithThrowableCard(t *testing.T) {
	m := NewDurakModel(3)
	m.humanIdx = 2
	setupBout(m.game, 2, 1, Spades, map[int][]Card{
		0: {dc(Ten, Diamonds)},
		1: {dc(Seven, Clubs), dc(Jack, Diamonds)},
		2: {dc(Six, Clubs), dc(Six, Hearts)}, // вторая шестёрка подходит по рангу
	})
	if err := m.game.ThrowIn(dc(Six, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := m.game.Defend(dc(Seven, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if m.game.Phase != DurakPhaseThrowIn || m.game.CurrentThrower() != m.humanIdx {
		t.Fatalf("тест предполагает, что сейчас очередь человека подкидывать")
	}

	m.maybeScheduleBot()

	if m.game.CurrentThrower() != m.humanIdx || m.game.Phase != DurakPhaseThrowIn {
		t.Errorf("у человека есть подходящая карта — его очередь не должна была автоматически пропуститься")
	}
}

func TestDurakModelThrowInViaKeys(t *testing.T) {
	m := NewDurakModel(2)
	setupBout(m.game, m.humanIdx, 1-m.humanIdx, m.game.TrumpSuit, map[int][]Card{
		m.humanIdx: {c(Six, Clubs)},
	})
	m.cursor = 0

	next, _ := m.Update(key("enter"))
	m = next.(DurakModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка подкидывания: %s", m.message)
	}
	if m.game.Phase != DurakPhaseDefend {
		t.Fatalf("ожидалась фаза защиты после подкидывания")
	}
	if len(m.game.Table) != 1 {
		t.Fatalf("ожидалась 1 карта на столе")
	}
}

func TestDurakModelDefendViaKeys(t *testing.T) {
	m := NewDurakModel(2)
	setupBout(m.game, 1-m.humanIdx, m.humanIdx, Spades, map[int][]Card{
		1 - m.humanIdx: {c(Six, Clubs)},
		m.humanIdx:     {c(Seven, Clubs), c(Ten, Diamonds)}, // запасная карта, чтобы cap>1
	})
	if err := m.game.ThrowIn(c(Six, Clubs)); err != nil {
		t.Fatalf("не удалось подготовить стол: %v", err)
	}
	m.cursor = 0

	next, _ := m.Update(key("enter"))
	m = next.(DurakModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка защиты: %s", m.message)
	}
	if len(m.game.Table) == 0 || m.game.Table[0].Defend == nil {
		t.Fatalf("карта на столе должна быть отбита")
	}
}

func TestDurakModelTakeViaKeys(t *testing.T) {
	m := NewDurakModel(2)
	setupBout(m.game, 1-m.humanIdx, m.humanIdx, Spades, map[int][]Card{
		1 - m.humanIdx: {c(Eight, Clubs)},
		m.humanIdx:     {c(Six, Hearts)}, // не бьёт
	})
	if err := m.game.ThrowIn(c(Eight, Clubs)); err != nil {
		t.Fatalf("не удалось подготовить стол: %v", err)
	}

	next, _ := m.Update(key("t"))
	m = next.(DurakModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка взятия: %s", m.message)
	}
	if !hasCard(m.game.Players[m.humanIdx].Hand, c(Eight, Clubs)) {
		t.Fatalf("после взятия карта со стола должна была оказаться в руке")
	}
}

func TestDurakModelPassViaKeys(t *testing.T) {
	m := NewDurakModel(2)
	// у человека (игрока 0) достаточно карт на руке защищающегося,
	// чтобы после защиты лимит атаки не был исчерпан и наступила
	// фаза подкидывания, где единственный потенциальный подкидывающий
	// (при 2 игроках) — сам атакующий, то есть человек
	setupBout(m.game, m.humanIdx, 1-m.humanIdx, Spades, map[int][]Card{
		m.humanIdx:     {c(Six, Clubs), c(Nine, Hearts)},
		1 - m.humanIdx: {c(Seven, Clubs), c(Ten, Diamonds)},
	})
	if err := m.game.ThrowIn(c(Six, Clubs)); err != nil {
		t.Fatalf("ошибка подготовки: %v", err)
	}
	if err := m.game.Defend(c(Seven, Clubs)); err != nil {
		t.Fatalf("ошибка подготовки: %v", err)
	}
	if m.game.Phase != DurakPhaseThrowIn {
		t.Fatalf("ожидалась фаза подкидывания, получено %v", m.game.Phase)
	}

	next, _ := m.Update(key("p"))
	m = next.(DurakModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка паса: %s", m.message)
	}
	if len(m.game.Table) != 0 {
		t.Fatalf("стол должен был очиститься после единственного паса (2 игрока)")
	}
}

func TestDurakModelOtherPlayerThrowInViaKeys(t *testing.T) {
	// человек — третий игрок (не атакующий и не защищающийся),
	// проверяем, что именно ему через TUI дают возможность подкинуть
	m := NewDurakModel(3)
	humanIdx := m.humanIdx
	var otherIdx int
	for i := range m.game.Players {
		if i != humanIdx {
			otherIdx = i
			break
		}
	}
	var thirdIdx int
	for i := range m.game.Players {
		if i != humanIdx && i != otherIdx {
			thirdIdx = i
			break
		}
	}
	setupBout(m.game, otherIdx, thirdIdx, Spades, map[int][]Card{
		otherIdx: {c(Six, Clubs)},
		thirdIdx: {c(Seven, Clubs), c(Ten, Diamonds), c(Jack, Diamonds)},
		humanIdx: {c(Six, Hearts)},
	})
	if err := m.game.ThrowIn(c(Six, Clubs)); err != nil {
		t.Fatalf("ошибка подготовки: %v", err)
	}
	if err := m.game.Defend(c(Seven, Clubs)); err != nil {
		t.Fatalf("ошибка подготовки: %v", err)
	}
	if m.game.CurrentThrower() != otherIdx {
		t.Fatalf("первым в очереди подкидывающих должен быть атакующий раунда")
	}

	// атакующий (бот) пасует программно, чтобы дошла очередь до человека
	if err := m.game.Pass(); err != nil {
		t.Fatalf("ошибка подготовки паса: %v", err)
	}
	if m.game.CurrentThrower() != humanIdx {
		t.Fatalf("ожидалось, что очередь дошла до человека (%d), получено %d", humanIdx, m.game.CurrentThrower())
	}

	m.cursor = 0
	next, _ := m.Update(key("enter"))
	m = next.(DurakModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка подкидывания человеком: %s", m.message)
	}
	if len(m.game.Table) != 2 {
		t.Fatalf("ожидалось 2 карты на столе после подкидывания человеком")
	}
}

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

// TestDurakModelFullAutoPlaythrough прогоняет TUI-модель Дурака от
// начала до конца, где "человек" отыгрывает свои ходы через
// реальные нажатия клавиш (используя ту же логику, что и DurakBot),
// а не напрямую через DurakGameState — проверяем весь путь через
// Update целиком для нескольких вариантов числа игроков.
func TestDurakModelFullAutoPlaythrough(t *testing.T) {
	for n := 2; n <= 6; n++ {
		m := NewDurakModel(n)
		const maxSteps = 10000
		steps := 0
		for m.game.Phase != DurakPhaseGameOver && steps < maxSteps {
			steps++
			if m.currentActor() != m.humanIdx {
				next, _ := m.Update(botMoveMsg{})
				m = next.(DurakModel)
				continue
			}

			selectCard := func(card Card) {
				for i, hc := range m.currentHand() {
					if hc == card {
						m.cursor = i
						break
					}
				}
			}

			switch m.game.Phase {
			case DurakPhaseAttack:
				card, _ := m.bot.DecideAttack(m.game)
				selectCard(card)
				next, _ := m.Update(key("enter"))
				m = next.(DurakModel)
			case DurakPhaseDefend:
				if card, ok := m.bot.DecideDefend(m.game); ok {
					selectCard(card)
					next, _ := m.Update(key("enter"))
					m = next.(DurakModel)
				} else {
					next, _ := m.Update(key("t"))
					m = next.(DurakModel)
				}
			case DurakPhaseThrowIn:
				if card, ok := m.bot.DecideThrowOrPass(m.game); ok {
					selectCard(card)
					next, _ := m.Update(key("enter"))
					m = next.(DurakModel)
				} else {
					next, _ := m.Update(key("p"))
					m = next.(DurakModel)
				}
			}
		}
		if steps >= maxSteps {
			t.Fatalf("игроков %d: партия через TUI не завершилась за %d шагов", n, steps)
		}
		if m.game.Result == nil {
			t.Fatalf("игроков %d: партия завершилась без результата", n)
		}
	}
}
