package main

import "testing"

func cp(r, c int) ChessPos { return ChessPos{Row: r, Col: c} }

func TestNewChessGameSetup(t *testing.T) {
	g := NewChessGame()
	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 p.Color == ChessWhite {
				whiteCount++
			} else {
				blackCount++
			}
		}
	}
	if whiteCount != 16 || blackCount != 16 {
		t.Errorf("ожидалось по 16 фигур, получено white=%d black=%d", whiteCount, blackCount)
	}
	if g.Turn != ChessWhite {
		t.Errorf("первый ход должен быть за белыми")
	}
	if g.Board[7][4].Type != ChessKing || g.Board[7][3].Type != ChessQueen {
		t.Errorf("король и ферзь белых должны стоять на своих клетках")
	}
}

func TestPawnDoubleStepAndSingleStep(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[6][4] = &ChessPiece{Color: ChessWhite, Type: ChessPawn}

	if err := g.Move(cp(6, 4), cp(4, 4)); err != nil {
		t.Fatalf("неожиданная ошибка хода пешкой на 2: %v", err)
	}
	if g.EnPassantTarget == nil || *g.EnPassantTarget != cp(5, 4) {
		t.Errorf("ожидалась цель взятия на проходе (5,4)")
	}
}

func TestPawnCannotJumpOverPiece(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[6][4] = &ChessPiece{Color: ChessWhite, Type: ChessPawn}
	g.Board[5][4] = &ChessPiece{Color: ChessBlack, Type: ChessPawn}

	if err := g.Move(cp(6, 4), cp(4, 4)); err != ErrChessInvalidMove {
		t.Errorf("пешка не должна перепрыгивать через фигуру, получено: %v", err)
	}
}

func TestPawnCapturesDiagonally(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[6][4] = &ChessPiece{Color: ChessWhite, Type: ChessPawn}
	g.Board[5][5] = &ChessPiece{Color: ChessBlack, Type: ChessPawn}

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

func TestEnPassantCapture(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[3][4] = &ChessPiece{Color: ChessWhite, Type: ChessPawn}
	g.Board[1][3] = &ChessPiece{Color: ChessBlack, Type: ChessPawn}

	g.Turn = ChessBlack
	if err := g.Move(cp(1, 3), cp(3, 3)); err != nil {
		t.Fatalf("неожиданная ошибка хода чёрной пешкой на 2: %v", err)
	}
	if g.EnPassantTarget == nil || *g.EnPassantTarget != cp(2, 3) {
		t.Fatalf("ожидалась цель взятия на проходе (2,3)")
	}

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

func TestEnPassantOnlyAvailableImmediately(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[3][4] = &ChessPiece{Color: ChessWhite, Type: ChessPawn}
	g.Board[1][3] = &ChessPiece{Color: ChessBlack, Type: ChessPawn}
	g.Board[6][0] = &ChessPiece{Color: ChessWhite, Type: ChessPawn}
	g.Board[1][0] = &ChessPiece{Color: ChessBlack, Type: ChessPawn}

	g.Turn = ChessBlack
	if err := g.Move(cp(1, 3), cp(3, 3)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.Move(cp(6, 0), cp(5, 0)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.Move(cp(1, 0), cp(2, 0)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	opts := g.LegalMovesFrom(cp(3, 4))
	for _, o := range opts {
		if o.IsEnPassant {
			t.Errorf("право на взятие на проходе должно было сгореть")
		}
	}
}

func TestPromotionToQueen(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[1][0] = &ChessPiece{Color: ChessWhite, Type: ChessPawn}
	g.Board[0][7] = &ChessPiece{Color: ChessBlack, Type: ChessKing}
	g.Board[7][7] = &ChessPiece{Color: ChessWhite, Type: ChessKing}

	if err := g.Move(cp(1, 0), cp(0, 0)); err != nil {
		t.Fatalf("неожиданная ошибка превращения: %v", err)
	}
	if g.Board[0][0] == nil || g.Board[0][0].Type != ChessQueen {
		t.Errorf("пешка должна была превратиться в ферзя")
	}
}

func TestKnightMovesInLShape(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[4][4] = &ChessPiece{Color: ChessWhite, Type: ChessKnight}
	opts := g.LegalMovesFrom(cp(4, 4))
	want := map[ChessPos]bool{
		cp(2, 3): true, cp(2, 5): true, cp(3, 2): true, cp(3, 6): true,
		cp(5, 2): true, cp(5, 6): true, cp(6, 3): true, cp(6, 5): true,
	}
	if len(opts) != 8 {
		t.Fatalf("ожидалось 8 ходов коня из центра доски, получено %d", len(opts))
	}
	for _, o := range opts {
		if !want[o.To] {
			t.Errorf("неожиданный ход коня на %v", o.To)
		}
	}
}

func TestBishopBlockedByOwnPiece(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[4][4] = &ChessPiece{Color: ChessWhite, Type: ChessBishop}
	g.Board[2][2] = &ChessPiece{Color: ChessWhite, Type: ChessPawn}
	opts := g.LegalMovesFrom(cp(4, 4))
	for _, o := range opts {
		if o.To == cp(2, 2) || o.To == cp(1, 1) {
			t.Errorf("слон не должен проходить через свою же фигуру или вставать на неё")
		}
	}
}

func TestRookCapturesEnemyButStops(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[4][4] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	g.Board[4][6] = &ChessPiece{Color: ChessBlack, Type: ChessPawn}
	opts := g.LegalMovesFrom(cp(4, 4))
	foundCapture, foundBeyond := false, false
	for _, o := range opts {
		if o.To == cp(4, 6) {
			foundCapture = true
		}
		if o.To == cp(4, 7) {
			foundBeyond = true
		}
	}
	if !foundCapture {
		t.Errorf("ладья должна была иметь возможность взять вражескую пешку")
	}
	if foundBeyond {
		t.Errorf("ладья не должна проходить сквозь взятую фигуру")
	}
}

func TestCastlingKingsideWhenClear(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[7][7] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	g.Board[0][4] = &ChessPiece{Color: ChessBlack, Type: ChessKing}

	if err := g.Move(cp(7, 4), cp(7, 6)); err != nil {
		t.Fatalf("неожиданная ошибка короткой рокировки: %v", err)
	}
	if g.Board[7][6] == nil || g.Board[7][6].Type != ChessKing {
		t.Errorf("король должен был оказаться на g1")
	}
	if g.Board[7][5] == nil || g.Board[7][5].Type != ChessRook {
		t.Errorf("ладья должна была оказаться на f1")
	}
	if g.Board[7][7] != nil {
		t.Errorf("исходная клетка ладьи должна была опустеть")
	}
}

func TestCastlingQueensideWhenClear(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[7][0] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	g.Board[0][4] = &ChessPiece{Color: ChessBlack, Type: ChessKing}

	if err := g.Move(cp(7, 4), cp(7, 2)); err != nil {
		t.Fatalf("неожиданная ошибка длинной рокировки: %v", err)
	}
	if g.Board[7][2] == nil || g.Board[7][2].Type != ChessKing {
		t.Errorf("король должен был оказаться на c1")
	}
	if g.Board[7][3] == nil || g.Board[7][3].Type != ChessRook {
		t.Errorf("ладья должна была оказаться на d1")
	}
}

func TestCastlingForbiddenAfterKingMoved(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[7][7] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	g.Board[0][4] = &ChessPiece{Color: ChessBlack, Type: ChessKing}
	g.WhiteKingMoved = true

	opts := g.LegalMovesFrom(cp(7, 4))
	for _, o := range opts {
		if o.IsCastleKingside {
			t.Errorf("рокировка должна быть запрещена, если король уже ходил")
		}
	}
}

func TestCastlingForbiddenWhileInCheck(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[7][7] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	g.Board[0][4] = &ChessPiece{Color: ChessBlack, Type: ChessRook} // держит короля под шахом по вертикали e

	opts := g.LegalMovesFrom(cp(7, 4))
	for _, o := range opts {
		if o.IsCastleKingside || o.IsCastleQueenside {
			t.Errorf("рокировка должна быть запрещена под шахом")
		}
	}
}

func TestCastlingForbiddenThroughAttackedSquare(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[7][7] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	g.Board[0][5] = &ChessPiece{Color: ChessBlack, Type: ChessRook} // бьёт f1 (клетка, через которую проходит король)

	opts := g.LegalMovesFrom(cp(7, 4))
	for _, o := range opts {
		if o.IsCastleKingside {
			t.Errorf("рокировка должна быть запрещена, если король проходит через атакованную клетку")
		}
	}
}

func TestCannotMoveIntoCheck(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[0][3] = &ChessPiece{Color: ChessBlack, Type: ChessRook}

	if err := g.Move(cp(7, 4), cp(7, 3)); err != ErrChessInvalidMove {
		t.Errorf("нельзя вставать королём под шах, ожидалась ошибка, получено: %v", err)
	}
}

func TestPinnedPieceCannotMove(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[6][4] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	g.Board[0][4] = &ChessPiece{Color: ChessBlack, Type: ChessRook}

	opts := g.LegalMovesFrom(cp(6, 4))
	for _, o := range opts {
		if o.To.Col != 4 {
			t.Errorf("связанная ладья не должна иметь возможность сойти с вертикали e, ход на %v", o.To)
		}
	}
}

func TestFoolsMateCheckmate(t *testing.T) {
	g := NewChessGame()
	moves := []struct{ from, to ChessPos }{
		{cp(6, 5), cp(5, 5)}, // f2-f3
		{cp(1, 4), cp(3, 4)}, // e7-e5
		{cp(6, 6), cp(4, 6)}, // g2-g4
		{cp(0, 3), cp(4, 7)}, // Qd8-h4#
	}
	for i, mv := range moves {
		if err := g.Move(mv.from, mv.to); err != nil {
			t.Fatalf("ход %d неожиданно не прошёл: %v", i, err)
		}
	}
	if g.Result == nil || !g.Result.Checkmate {
		t.Fatalf("ожидался мат ('детский мат'), получено: %+v", g.Result)
	}
	if g.Result.Winner != ChessBlack {
		t.Errorf("ожидался победитель — чёрные")
	}
}

func TestStalemateDraw(t *testing.T) {
	g := &ChessGameState{Turn: ChessBlack}
	g.Board[0][7] = &ChessPiece{Color: ChessBlack, Type: ChessKing}  // h8
	g.Board[2][6] = &ChessPiece{Color: ChessWhite, Type: ChessQueen} // g6
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}  // e1, далеко и не мешает

	if g.inCheck(ChessBlack) {
		t.Fatalf("в этой тестовой позиции чёрный король не должен быть под шахом")
	}
	if g.hasAnyLegalMove(ChessBlack) {
		t.Fatalf("в этой тестовой позиции у чёрных не должно быть легальных ходов")
	}
	g.checkGameOver()
	if g.Result == nil || !g.Result.Draw || g.Result.DrawReason != "stalemate" {
		t.Fatalf("ожидался пат, получено: %+v", g.Result)
	}
}

func TestInsufficientMaterialKingVsKing(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[0][0] = &ChessPiece{Color: ChessBlack, Type: ChessKing}
	g.Board[7][7] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	if !g.hasInsufficientMaterial() {
		t.Errorf("король против короля — недостаточно материала для мата")
	}
}

func TestSufficientMaterialWithRook(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[0][0] = &ChessPiece{Color: ChessBlack, Type: ChessKing}
	g.Board[7][7] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[4][4] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	if g.hasInsufficientMaterial() {
		t.Errorf("с ладьёй на доске материала достаточно для мата")
	}
}

func TestChessCannotMoveOpponentPiece(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite}
	g.Board[1][4] = &ChessPiece{Color: ChessBlack, Type: ChessPawn}

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

func TestNoProgressDrawLimit(t *testing.T) {
	g := &ChessGameState{Turn: ChessWhite, HalfmoveClock: maxChessHalfmovesWithoutProgress - 1}
	g.Board[7][4] = &ChessPiece{Color: ChessWhite, Type: ChessKing}
	g.Board[7][0] = &ChessPiece{Color: ChessWhite, Type: ChessRook}
	g.Board[0][4] = &ChessPiece{Color: ChessBlack, Type: ChessKing}
	g.Board[0][7] = &ChessPiece{Color: ChessBlack, Type: ChessRook}

	if err := g.Move(cp(7, 0), cp(6, 0)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Result == nil || !g.Result.Draw || g.Result.DrawReason != "no_progress" {
		t.Fatalf("ожидалась ничья по правилу отсутствия прогресса, получено: %+v", g.Result)
	}
}
