package main

import (
	"strings"
	"testing"

	"github.com/charmbracelet/lipgloss"
	"github.com/muesli/termenv"
)

// TestCheckersModelCursorVisibleOnLegalDestination проверяет тот же
// баг, что и в Шахматах: курсор не должен теряться под подсветкой
// легального хода.
func TestCheckersModelCursorVisibleOnLegalDestination(t *testing.T) {
	oldProfile := lipgloss.ColorProfile()
	lipgloss.SetColorProfile(termenv.ANSI256)
	defer lipgloss.SetColorProfile(oldProfile)

	m := NewCheckersModel(0, ColorChoiceA)
	m.cursor = CheckersPos{Row: 5, Col: 2}
	next, _ := m.Update(key("enter")) // выбираем шашку
	m = next.(CheckersModel)

	dests := m.legalDestinationSet()
	var destPos CheckersPos
	for pos := range dests {
		destPos = pos
		break
	}
	m.cursor = destPos

	board := m.renderBoard()
	cursorTag := checkersCursorStyle.Render("X")
	start := strings.Index(cursorTag, "\x1b[")
	end := strings.Index(cursorTag, "m")
	cursorColorCode := cursorTag[start : end+1]
	if !strings.Contains(board, cursorColorCode) {
		t.Errorf("цвет курсора должен присутствовать в отрисовке доски даже на клетке легального хода")
	}
}

// TestCheckersCursorOnPieceUsesOneCombinedStyle проверяет исправление
// более глубокого бага: раньше цвет фигуры применялся ВНУТРИ уже
// готового Render() вызова, что при последующем оборачивании в фон
// клетки (курсор/подсветка) давало вложенные ANSI-последовательности
// с преждевременным сбросом — фон обрывался до конца клетки (тот же
// класс бага, что раньше был в отрисовке карт Тонка).
func TestCheckersCursorOnPieceUsesOneCombinedStyle(t *testing.T) {
	oldProfile := lipgloss.ColorProfile()
	lipgloss.SetColorProfile(termenv.ANSI256)
	defer lipgloss.SetColorProfile(oldProfile)

	piece := &CheckersPiece{Color: CheckersBlack}
	text := pieceGlyphChar(piece)
	fg, ok := pieceForegroundColor(piece)
	if !ok {
		t.Fatalf("ожидался цвет для непустой клетки")
	}
	combined := checkersCursorStyle.Foreground(fg).Bold(true)
	rendered := combined.Render(" " + text + " ")

	resets := strings.Count(rendered, "\x1b[0m")
	if resets != 1 {
		t.Errorf("ожидался ровно один сброс ANSI на всю клетку (единый стиль), получено %d: %q", resets, rendered)
	}
	opens := strings.Count(rendered, "\x1b[")
	if opens != 2 {
		t.Errorf("ожидалось ровно 2 вхождения ESC (открытие+закрытие, без вложенности), получено %d: %q", opens, rendered)
	}
}

func TestCheckersModelSelectAndMoveViaKeys(t *testing.T) {
	m := NewCheckersModel(0, ColorChoiceA)
	m.cursor = CheckersPos{Row: 5, Col: 2}

	next, _ := m.Update(key("enter"))
	m = next.(CheckersModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка выбора: %s", m.message)
	}
	if m.selected == nil {
		t.Fatalf("ожидался выбор шашки")
	}

	m.cursor = CheckersPos{Row: 4, Col: 1}
	next, _ = m.Update(key("enter"))
	m = next.(CheckersModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка хода: %s", m.message)
	}
	if m.game.Board[4][1] == nil {
		t.Fatalf("шашка должна была переместиться")
	}
	if m.selected != nil {
		t.Errorf("выбор должен был сброситься после хода")
	}
}

func TestCheckersModelCursorMovement(t *testing.T) {
	m := NewCheckersModel(0, ColorChoiceA)
	m.cursor = CheckersPos{Row: 4, Col: 4}

	next, _ := m.Update(key("up"))
	m = next.(CheckersModel)
	if m.cursor.Row != 3 {
		t.Errorf("курсор должен был сдвинуться вверх")
	}
	next, _ = m.Update(key("right"))
	m = next.(CheckersModel)
	if m.cursor.Col != 5 {
		t.Errorf("курсор должен был сдвинуться вправо")
	}
}

func TestCheckersModelEscClearsSelection(t *testing.T) {
	m := NewCheckersModel(0, ColorChoiceA)
	m.cursor = CheckersPos{Row: 5, Col: 2}
	next, _ := m.Update(key("enter"))
	m = next.(CheckersModel)
	if m.selected == nil {
		t.Fatalf("ожидался выбор")
	}

	next, _ = m.Update(key("esc"))
	m = next.(CheckersModel)
	if m.selected != nil {
		t.Errorf("esc должен был сбросить выбор")
	}
}

func TestCheckersModelCannotSelectEmptySquare(t *testing.T) {
	m := NewCheckersModel(0, ColorChoiceA)
	m.cursor = CheckersPos{Row: 3, Col: 2}

	next, _ := m.Update(key("enter"))
	m = next.(CheckersModel)
	if !m.isError {
		t.Fatalf("ожидалась ошибка выбора пустой клетки")
	}
}

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

func TestCheckersModelNewGameAfterResult(t *testing.T) {
	m := NewCheckersModel(0, ColorChoiceA)
	m.game.Result = &CheckersResult{Winner: CheckersWhite}

	next, _ := m.Update(key("n"))
	m = next.(CheckersModel)
	if m.game.Result != nil {
		t.Errorf("новая партия не должна была начаться с результатом")
	}
}

// TestCheckersModelFullAutoPlaythrough прогоняет TUI-модель от
// начала до конца, где "человек" отыгрывает свои ходы через бота
// (небольшая глубина — ради скорости), проверяя, что весь путь
// через Update не паникует и партия завершается.
func TestCheckersModelFullAutoPlaythrough(t *testing.T) {
	m := NewCheckersModel(0, ColorChoiceA)
	m.bot = CheckersBot{Depth: 2}
	helperBot := CheckersBot{Depth: 2}

	const maxSteps = 400
	for i := 0; i < maxSteps && m.game.Result == nil; i++ {
		if m.game.Turn != m.human {
			next, _ := m.Update(botMoveMsg{})
			m = next.(CheckersModel)
			continue
		}
		mv, ok := helperBot.DecideMove(m.game)
		if !ok {
			break
		}
		m.cursor = mv.From
		next, _ := m.Update(key("enter"))
		m = next.(CheckersModel)
		if m.isError {
			t.Fatalf("неожиданная ошибка выбора хода человеком: %s", m.message)
		}
		m.cursor = mv.To
		next, _ = m.Update(key("enter"))
		m = next.(CheckersModel)
		if m.isError {
			t.Fatalf("неожиданная ошибка хода человеком: %s", m.message)
		}
	}
	if m.game.Result == nil {
		t.Fatalf("партия через TUI не завершилась за %d шагов", maxSteps)
	}
}

func TestNewCheckersModelRespectsColorChoiceWhite(t *testing.T) {
	m := NewCheckersModel(0, ColorChoiceA)
	if m.human != CheckersWhite {
		t.Errorf("при выборе ColorChoiceA человек должен был играть белыми, получено %v", m.human)
	}
}

func TestNewCheckersModelRespectsColorChoiceBlack(t *testing.T) {
	m := NewCheckersModel(0, ColorChoiceB)
	if m.human != CheckersBlack {
		t.Errorf("при выборе ColorChoiceB человек должен был играть чёрными, получено %v", m.human)
	}
}

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