package main

import "testing"

func TestNewCheckersGameSetup(t *testing.T) {
	g := NewCheckersGame()
	whiteCount, blackCount := 0, 0
	for r := 0; r < 8; r++ {
		for c := 0; c < 8; c++ {
			p := g.Board[r][c]
			if p == nil {
				continue
			}
			if (r+c)%2 == 0 {
				t.Errorf("шашка на светлой клетке (%d,%d)", r, c)
			}
			if p.Color == CheckersWhite {
				whiteCount++
			} else {
				blackCount++
			}
		}
	}
	if whiteCount != 12 || blackCount != 12 {
		t.Errorf("ожидалось по 12 шашек, получено white=%d black=%d", whiteCount, blackCount)
	}
	if g.Turn != CheckersWhite {
		t.Errorf("первый ход должен быть за белыми")
	}
}

func TestSimpleManMoveForwardOnly(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[5][2] = &CheckersPiece{Color: CheckersWhite}

	if err := g.Move(CheckersPos{5, 2}, CheckersPos{4, 1}); err != nil {
		t.Fatalf("неожиданная ошибка хода вперёд: %v", err)
	}
}

func TestSimpleManCannotMoveBackward(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[5][2] = &CheckersPiece{Color: CheckersWhite}

	if err := g.Move(CheckersPos{5, 2}, CheckersPos{6, 1}); err != ErrCheckersInvalidMove {
		t.Errorf("простая шашка не должна ходить назад, ожидалась ErrCheckersInvalidMove, получено: %v", err)
	}
}

func TestManCapturesBackward(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[4][3] = &CheckersPiece{Color: CheckersWhite}
	g.Board[5][4] = &CheckersPiece{Color: CheckersBlack}

	if err := g.Move(CheckersPos{4, 3}, CheckersPos{6, 5}); err != nil {
		t.Fatalf("шашка должна была взять назад: %v", err)
	}
	if g.Board[5][4] != nil {
		t.Errorf("взятая шашка должна была исчезнуть с доски")
	}
	if g.Board[6][5] == nil {
		t.Errorf("шашка должна была оказаться на клетке приземления")
	}
}

func TestCaptureIsMandatory(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[5][2] = &CheckersPiece{Color: CheckersWhite}
	g.Board[4][3] = &CheckersPiece{Color: CheckersBlack}
	g.Board[5][6] = &CheckersPiece{Color: CheckersWhite}

	if err := g.Move(CheckersPos{5, 6}, CheckersPos{4, 5}); err != ErrCheckersMustCapture {
		t.Errorf("простой ход должен быть запрещён при доступном взятии, получено: %v", err)
	}
	if err := g.Move(CheckersPos{5, 2}, CheckersPos{3, 4}); err != nil {
		t.Fatalf("взятие должно было пройти: %v", err)
	}
}

func TestMultiJumpMustContinueWithSamePiece(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[6][1] = &CheckersPiece{Color: CheckersWhite}
	g.Board[5][2] = &CheckersPiece{Color: CheckersBlack}
	g.Board[3][4] = &CheckersPiece{Color: CheckersBlack}
	g.Board[6][7] = &CheckersPiece{Color: CheckersWhite} // другая белая шашка — ходить ею сейчас нельзя

	if err := g.Move(CheckersPos{6, 1}, CheckersPos{4, 3}); err != nil {
		t.Fatalf("первое взятие должно было пройти: %v", err)
	}
	if g.ChainPos == nil {
		t.Fatalf("ожидалось продолжение серии взятий той же шашкой")
	}
	if g.Turn != CheckersWhite {
		t.Errorf("ход не должен был перейти сопернику в середине серии")
	}

	if err := g.Move(CheckersPos{6, 7}, CheckersPos{5, 6}); err != ErrCheckersMustContinue {
		t.Errorf("ход другой шашкой в середине серии взятий должен быть запрещён, получено: %v", err)
	}

	if err := g.Move(CheckersPos{4, 3}, CheckersPos{2, 5}); err != nil {
		t.Fatalf("второе взятие в серии должно было пройти: %v", err)
	}
	if g.ChainPos != nil {
		t.Errorf("серия должна была завершиться — больше взятий нет")
	}
	if g.Turn != CheckersBlack {
		t.Errorf("ход должен был перейти чёрным после завершения серии")
	}
	if g.Board[5][2] != nil || g.Board[3][4] != nil {
		t.Errorf("обе взятые шашки должны были исчезнуть с доски")
	}
}

func TestPromotionToKing(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[1][2] = &CheckersPiece{Color: CheckersWhite}

	if err := g.Move(CheckersPos{1, 2}, CheckersPos{0, 1}); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if !g.Board[0][1].King {
		t.Errorf("шашка должна была стать дамкой на последней горизонтали")
	}
}

func TestPromotionDuringCaptureContinuesAsKing(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[2][1] = &CheckersPiece{Color: CheckersWhite}
	g.Board[1][2] = &CheckersPiece{Color: CheckersBlack}
	g.Board[2][5] = &CheckersPiece{Color: CheckersBlack}

	if err := g.Move(CheckersPos{2, 1}, CheckersPos{0, 3}); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if !g.Board[0][3].King {
		t.Fatalf("шашка должна была стать дамкой при взятии на последней горизонтали")
	}
	if g.ChainPos == nil {
		t.Fatalf("если дальнейшее взятие возможно дамкой — серия должна продолжиться")
	}
}

func TestKingFlyingMove(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[4][4] = &CheckersPiece{Color: CheckersWhite, King: true}

	if err := g.Move(CheckersPos{4, 4}, CheckersPos{1, 1}); err != nil {
		t.Fatalf("дамка должна летать на любое расстояние по диагонали: %v", err)
	}
}

func TestKingFlyingCaptureWithChoiceOfLanding(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[7][0] = &CheckersPiece{Color: CheckersWhite, King: true}
	g.Board[4][3] = &CheckersPiece{Color: CheckersBlack}

	options := g.captureMoveOptions(CheckersPos{7, 0})
	if len(options) == 0 {
		t.Fatalf("ожидались варианты взятия дамкой")
	}
	landed := false
	for _, opt := range options {
		if opt.To == (CheckersPos{2, 5}) {
			landed = true
		}
	}
	if !landed {
		t.Errorf("среди вариантов должна быть клетка (2,5)")
	}

	if err := g.Move(CheckersPos{7, 0}, CheckersPos{1, 6}); err != nil {
		t.Fatalf("выбор дальней клетки приземления должен быть допустим: %v", err)
	}
	if g.Board[4][3] != nil {
		t.Errorf("взятая шашка должна была исчезнуть")
	}
}

func TestKingCannotJumpOverTwoPiecesInARow(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[7][0] = &CheckersPiece{Color: CheckersWhite, King: true}
	g.Board[4][3] = &CheckersPiece{Color: CheckersBlack}
	g.Board[3][4] = &CheckersPiece{Color: CheckersBlack}

	options := g.captureMoveOptions(CheckersPos{7, 0})
	for _, opt := range options {
		if opt.To.Row <= 3 {
			t.Errorf("нельзя приземлиться за второй подряд фигурой, но получен вариант %v", opt.To)
		}
	}
}

func TestGameOverWhenNoLegalMoves(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersBlack}
	g.Board[0][7] = &CheckersPiece{Color: CheckersBlack}
	g.Board[1][6] = &CheckersPiece{Color: CheckersWhite}
	// (2,5) занята — иначе чёрная шашка могла бы взять белую назад
	// через прыжок (0,7)->(2,5), ведь простые шашки в русских шашках
	// бьют в любом направлении, включая назад
	g.Board[2][5] = &CheckersPiece{Color: CheckersWhite}
	g.Board[5][0] = &CheckersPiece{Color: CheckersWhite}

	g.Turn = CheckersWhite
	if err := g.Move(CheckersPos{5, 0}, CheckersPos{4, 1}); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Result == nil {
		t.Fatalf("партия должна была завершиться — у чёрных нет ходов")
	}
	if g.Result.Winner != CheckersWhite {
		t.Errorf("ожидался победитель — белые")
	}
}

func TestDrawByNoCaptureLimit(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite, MovesSinceCapture: maxCheckersMovesWithoutCapture - 1}
	g.Board[5][2] = &CheckersPiece{Color: CheckersWhite}

	if err := g.Move(CheckersPos{5, 2}, CheckersPos{4, 1}); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Result == nil || !g.Result.Draw {
		t.Fatalf("ожидалась ничья по лимиту ходов без взятия")
	}
}

func TestCannotMoveOpponentPiece(t *testing.T) {
	g := &CheckersGameState{Turn: CheckersWhite}
	g.Board[2][1] = &CheckersPiece{Color: CheckersBlack}

	if err := g.Move(CheckersPos{2, 1}, CheckersPos{3, 2}); err != ErrCheckersNoPiece {
		t.Errorf("ожидалась ошибка хода чужой шашкой, получено: %v", err)
	}
}
