package main

import "testing"

func TestSenetStartingSetupAlternates(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	for sq := 1; sq <= 10; sq++ {
		owner, ok := senetCellOwner(g.Board[sq])
		if !ok {
			t.Fatalf("клетка %d должна быть занята при старте", sq)
		}
		wantA := sq%2 == 1
		if wantA && owner != SenetPlayerA {
			t.Errorf("клетка %d (нечётная) должна принадлежать игроку A", sq)
		}
		if !wantA && owner != SenetPlayerB {
			t.Errorf("клетка %d (чётная) должна принадлежать игроку B", sq)
		}
	}
	for sq := 11; sq <= 30; sq++ {
		if g.Board[sq] != 0 {
			t.Errorf("клетка %d должна быть пуста при старте, получено %d", sq, g.Board[sq])
		}
	}
}

func TestSenetSwapOnLoneOpponentBlot(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[5] = senetCellValue(SenetPlayerA)
	g.Board[8] = senetCellValue(SenetPlayerB)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 3

	if err := g.Move(5); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	ownerAt8, _ := senetCellOwner(g.Board[8])
	ownerAt5, _ := senetCellOwner(g.Board[5])
	if ownerAt8 != SenetPlayerA {
		t.Errorf("клетка 8 должна была стать занятой игроком A, получено %v", g.Board[8])
	}
	if ownerAt5 != SenetPlayerB {
		t.Errorf("шашка соперника должна была обменяться местами и оказаться на клетке 5, получено %v", g.Board[5])
	}
}

func TestSenetTwoInRowAreProtected(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[5] = senetCellValue(SenetPlayerA)
	g.Board[20] = senetCellValue(SenetPlayerA) // вторая шашка — есть куда сходить вперёд, так что откат "хода назад" не включится
	g.Board[8] = senetCellValue(SenetPlayerB)
	g.Board[9] = senetCellValue(SenetPlayerB)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 3

	moves := g.LegalMoves()
	for _, mv := range moves {
		if mv.From == 5 && !mv.Backward {
			t.Errorf("нельзя вставать вперёд на защищённую (2+ подряд) шашку соперника")
		}
	}
}

func TestSenetThreeInRowBlockPassage(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[5] = senetCellValue(SenetPlayerA)
	g.Board[20] = senetCellValue(SenetPlayerA) // вторая шашка с явно легальным ходом — проверка не вакуумна
	g.Board[6] = senetCellValue(SenetPlayerB)
	g.Board[7] = senetCellValue(SenetPlayerB)
	g.Board[8] = senetCellValue(SenetPlayerB)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 5 // 5+5=10, должно пройти НАСКВОЗЬ через 6-7-8

	moves := g.LegalMoves()
	if len(moves) == 0 {
		t.Fatalf("ожидался хотя бы один ход (вторая шашка с 20 точно может сходить)")
	}
	for _, mv := range moves {
		if mv.From == 5 {
			t.Errorf("заграждение из трёх подряд должно полностью блокировать проход, даже если конечная клетка сама по себе свободна")
		}
	}
}

func TestSenetBlockadeDoesNotTurnCorner(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[9] = senetCellValue(SenetPlayerB)
	g.Board[10] = senetCellValue(SenetPlayerB)
	g.Board[11] = senetCellValue(SenetPlayerB)
	if g.senetRunLength(10) >= 3 {
		t.Errorf("цепочка не должна пересекать угол 10-11, получено %d", g.senetRunLength(10))
	}
}

func TestSenetTrapSquareSendsBackToRebirth(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[24] = senetCellValue(SenetPlayerA)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 3 // 24+3=27 — ловушка

	if err := g.Move(24); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Board[27] != 0 {
		t.Errorf("клетка 27 должна была остаться пустой после отскока")
	}
	owner, ok := senetCellOwner(g.Board[SenetHouseRebirth])
	if !ok || owner != SenetPlayerA {
		t.Errorf("шашка должна была оказаться на клетке 15 (дом возрождения)")
	}
}

func TestSenetTrapBouncesToNearestEmptyBeforeRebirth(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[24] = senetCellValue(SenetPlayerA)
	g.Board[SenetHouseRebirth] = senetCellValue(SenetPlayerB)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 3

	if err := g.Move(24); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	owner, ok := senetCellOwner(g.Board[SenetHouseRebirth-1])
	if !ok || owner != SenetPlayerA {
		t.Errorf("при занятой клетке 15 шашка должна была отскочить на ближайшую свободную клетку перед ней (14)")
	}
}

func TestSenetExactRollRequiredToBearOffFromHouseThree(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[SenetHouseThree] = senetCellValue(SenetPlayerA)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 2 // House 28 требует ровно 3

	if err := g.Move(SenetHouseThree); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.BorneOff[SenetPlayerA] != 0 {
		t.Errorf("бросок 2 не должен был снять шашку с клетки 28 (нужен именно 3)")
	}
	owner, ok := senetCellOwner(g.Board[30])
	if !ok || owner != SenetPlayerA {
		t.Errorf("шашка должна была просто переместиться на клетку 30")
	}
}

func TestSenetBearsOffWithExactRoll(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[SenetHouseThree] = senetCellValue(SenetPlayerA)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 3

	if err := g.Move(SenetHouseThree); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.BorneOff[SenetPlayerA] != 1 {
		t.Errorf("ожидалось снятие шашки с клетки 28 точным броском 3, BorneOff=%d", g.BorneOff[SenetPlayerA])
	}
}

func TestSenetCannotBearOffWithoutReachingExitSquare(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[20] = senetCellValue(SenetPlayerA)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 5

	moves := g.LegalMoves()
	if len(moves) != 1 || moves[0].From != 20 {
		t.Fatalf("ожидался один обычный ход на клетку 25, получено %+v", moves)
	}
	g.Move(20)
	if g.BorneOff[SenetPlayerA] != 0 {
		t.Errorf("снятие не должно было произойти вдалеке от выходных клеток")
	}
}

func TestSenetWinWhenAllFiveBorneOff(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.BorneOff[SenetPlayerA] = SenetPieceCount - 1
	g.Board[SenetBoardSize] = senetCellValue(SenetPlayerA)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 1

	if err := g.Move(SenetBoardSize); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Result == nil || g.Result.Winner != SenetPlayerA {
		t.Fatalf("ожидалась победа игрока A, получено Result=%+v", g.Result)
	}
}

func TestSenetExtraTurnOnRolls1And4And5(t *testing.T) {
	for _, roll := range []int{1, 4, 5} {
		g := NewSenetGame(SenetPlayerA)
		g.Board = [SenetBoardSize + 1]int{}
		g.Board[1] = senetCellValue(SenetPlayerA)
		g.CurrentPlayer = SenetPlayerA
		g.Rolled = true
		g.DiceValue = roll
		g.Move(1)
		if g.CurrentPlayer != SenetPlayerA {
			t.Errorf("бросок %d должен был дать дополнительный ход тому же игроку", roll)
		}
	}
}

func TestSenetNoExtraTurnOnRolls2And3(t *testing.T) {
	for _, roll := range []int{2, 3} {
		g := NewSenetGame(SenetPlayerA)
		g.Board = [SenetBoardSize + 1]int{}
		g.Board[1] = senetCellValue(SenetPlayerA)
		g.CurrentPlayer = SenetPlayerA
		g.Rolled = true
		g.DiceValue = roll
		g.Move(1)
		if g.CurrentPlayer != SenetPlayerB {
			t.Errorf("бросок %d не должен был давать дополнительный ход", roll)
		}
	}
}

func TestSenetMustMoveBackwardWhenNoForwardMovePossible(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[10] = senetCellValue(SenetPlayerA)
	g.Board[13] = senetCellValue(SenetPlayerB)
	g.Board[14] = senetCellValue(SenetPlayerB)
	g.CurrentPlayer = SenetPlayerA
	g.Rolled = true
	g.DiceValue = 3

	moves := g.LegalMoves()
	if len(moves) == 0 {
		t.Fatalf("ожидался ход назад (10-3=7), так как вперёд ходов нет")
	}
	for _, mv := range moves {
		if !mv.Backward {
			t.Errorf("при отсутствии хода вперёд все предложенные ходы должны быть назад")
		}
	}
}

func TestSenetNoMoveAtAllEndsTurnAfterRoll(t *testing.T) {
	g := NewSenetGame(SenetPlayerA)
	g.Board = [SenetBoardSize + 1]int{}
	g.Board[10] = senetCellValue(SenetPlayerA)
	g.Board[12] = senetCellValue(SenetPlayerB)
	g.Board[13] = senetCellValue(SenetPlayerB)
	g.Board[14] = senetCellValue(SenetPlayerB)
	g.Board[6] = senetCellValue(SenetPlayerB)
	g.Board[7] = senetCellValue(SenetPlayerB)
	g.CurrentPlayer = SenetPlayerA

	if err := g.Roll(3); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.CurrentPlayer != SenetPlayerB {
		t.Errorf("при полном отсутствии ходов (ни вперёд, ни назад) очередь должна была перейти сопернику")
	}
}
