package main

import "testing"

func TestShortNardyStartingSetup(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	// белые: 2 на 23(abs), 5 на 12(abs), 3 на 7(abs), 5 на 5(abs)
	checkPoint := func(abs int, owner NardyPlayer, count int) {
		t.Helper()
		pt := g.Board[abs]
		if pt.Count != count || (count > 0 && pt.Owner != owner) {
			t.Errorf("точка %d: ожидалось %v x%d, получено %v x%d", abs, owner, count, pt.Owner, pt.Count)
		}
	}
	checkPoint(23, NardyWhite, 2)
	checkPoint(12, NardyWhite, 5)
	checkPoint(7, NardyWhite, 3)
	checkPoint(5, NardyWhite, 5)
	// чёрные — зеркально: rel 23 -> abs 23-23=0, rel12->abs11, rel7->abs16, rel5->abs18
	checkPoint(0, NardyBlack, 2)
	checkPoint(11, NardyBlack, 5)
	checkPoint(16, NardyBlack, 3)
	checkPoint(18, NardyBlack, 5)

	total := 0
	for _, pt := range g.Board {
		total += pt.Count
	}
	if total != 30 {
		t.Errorf("на доске должно быть 30 шашек суммарно, получено %d", total)
	}
}

func TestLongNardyStartingSetup(t *testing.T) {
	g := NewNardyGame(NardyLong, NardyWhite)
	if g.Board[23].Count != 15 || g.Board[23].Owner != NardyWhite {
		t.Errorf("все 15 белых шашек должны стоять на абсолютной точке 23, получено %+v", g.Board[23])
	}
	// чёрные: rel23 -> abs (23+12)%24=11
	if g.Board[11].Count != 15 || g.Board[11].Owner != NardyBlack {
		t.Errorf("все 15 чёрных шашек должны стоять на абсолютной точке 11, получено %+v", g.Board[11])
	}
}

func TestCanLandShortNardyBlocksOnTwoOrMore(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[10] = NardyPoint{Owner: NardyBlack, Count: 2}
	if g.canLand(NardyWhite, 10) {
		t.Errorf("точка с двумя и более шашками соперника должна быть заблокирована")
	}
}

func TestCanLandShortNardyAllowsHittingBlot(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[10] = NardyPoint{Owner: NardyBlack, Count: 1}
	if !g.canLand(NardyWhite, 10) {
		t.Errorf("одинокую шашку соперника (блот) должно быть можно сбить")
	}
}

func TestCanLandLongNardyBlocksEvenSingleChecker(t *testing.T) {
	g := NewNardyGame(NardyLong, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[10] = NardyPoint{Owner: NardyBlack, Count: 1}
	if g.canLand(NardyWhite, 10) {
		t.Errorf("в длинных нардах даже одна шашка соперника должна блокировать точку полностью")
	}
}

func TestHittingSendsBlotToBar(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[15] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.Board[10] = NardyPoint{Owner: NardyBlack, Count: 1}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{5}

	if err := g.ApplyMove(NardyMove{From: 15, Die: 5}); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Bar[NardyBlack] != 1 {
		t.Errorf("сбитая шашка чёрных должна была уйти на бар, Bar[Black]=%d", g.Bar[NardyBlack])
	}
	if g.Board[10].Owner != NardyWhite || g.Board[10].Count != 1 {
		t.Errorf("на точке 10 теперь должна стоять белая шашка, получено %+v", g.Board[10])
	}
}

func TestMustEnterFromBarBeforeOtherMoves(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[15] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.Bar[NardyWhite] = 1
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{5}

	moves := g.LegalMovesNow()
	for _, mv := range moves {
		if !mv.FromBar {
			t.Errorf("пока на баре есть шашка, разрешён только ход с бара, получен обычный ход %+v", mv)
		}
	}
	if len(moves) == 0 {
		t.Fatalf("ожидался хотя бы один легальный ход (вход с бара)")
	}
}

func TestBarEntryBlockedByOpponentPoint(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Bar[NardyWhite] = 1
	entryAbs := 24 - 3
	g.Board[entryAbs] = NardyPoint{Owner: NardyBlack, Count: 2}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{3}

	if err := g.ApplyMove(NardyMove{FromBar: true, Die: 3}); err != ErrNardyIllegalMove {
		t.Errorf("вход с бара на заблокированную точку должен быть отклонён, получено: %v", err)
	}
}

func TestBearOffRequiresAllCheckersHome(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[3] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.Board[15] = NardyPoint{Owner: NardyWhite, Count: 1} // шашка ещё вне дома
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{4}

	moves := g.LegalMovesNow()
	for _, mv := range moves {
		if mv.BearOff {
			t.Errorf("снятие с доски не должно быть доступно, пока не все шашки дома")
		}
	}
}

func TestBearOffExactMatch(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[3] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{4}

	if err := g.ApplyMove(NardyMove{From: 3, Die: 4, BearOff: true}); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.BorneOff[NardyWhite] != 1 {
		t.Errorf("ожидалось снятие одной шашки, BorneOff=%d", g.BorneOff[NardyWhite])
	}
}

func TestBearOffExcessRejectedWithCheckerBeyond(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[2] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.Board[4] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{6}

	moves := g.LegalMovesNow()
	for _, mv := range moves {
		if mv.BearOff && mv.From == 2 {
			t.Errorf("избыточное снятие с точки 2 костью 6 не должно быть разрешено, пока есть шашка на точке 4 (дальше)")
		}
	}
}

func TestBearOffExcessAllowedWithoutCheckerBeyond(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[2] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{6}

	if err := g.ApplyMove(NardyMove{From: 2, Die: 6, BearOff: true}); err != nil {
		t.Fatalf("избыточное снятие должно быть разрешено, если нет шашек дальше: %v", err)
	}
	if g.BorneOff[NardyWhite] != 1 {
		t.Errorf("ожидалось снятие шашки, получено BorneOff=%d", g.BorneOff[NardyWhite])
	}
}

func TestOnlyOneCheckerFromHeadPerTurn(t *testing.T) {
	g := NewNardyGame(NardyLong, NardyWhite)
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{3, 4}

	moves := g.LegalMovesNow()
	if len(moves) == 0 {
		t.Fatalf("ожидался хотя бы один легальный ход")
	}
	if err := g.ApplyMove(moves[0]); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	for _, mv := range g.LegalMovesNow() {
		if mv.From == 23 {
			t.Errorf("вторая шашка с головы в одном ходу быть взята не должна")
		}
	}
}

func TestDoublesGiveFourDiceValues(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Roll(4, 4)
	if len(g.DiceLeft) != 4 {
		t.Fatalf("дубль должен давать 4 значения кости, получено %d: %v", len(g.DiceLeft), g.DiceLeft)
	}
	for _, d := range g.DiceLeft {
		if d != 4 {
			t.Errorf("все четыре значения должны быть равны 4, получено %v", g.DiceLeft)
		}
	}
}

func TestMustUseMaximumPossibleDice(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	// шашка на rel=10: ходом 5 попадает на rel=5 (открыто), ходом 3 —
	// на rel=7 (заблокировано двумя чёрными). Если сыграть 5 первой,
	// потом с rel=5 можно сыграть 3 -> rel=2 (открыто) = используются
	// ОБЕ кости. Если сыграть 3 первой — ход блокирован сразу.
	g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.Board[7] = NardyPoint{Owner: NardyBlack, Count: 2}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{5, 3}

	moves := g.LegalMovesNow()
	if len(moves) != 1 || moves[0].Die != 5 || moves[0].From != 10 {
		t.Fatalf("единственный легальный первый ход должен быть 10->5 (используя большую кость 5), получено %+v", moves)
	}
}

func TestMustPlayLargerDieWhenOnlyOnePlayable(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.Board[8] = NardyPoint{Owner: NardyBlack, Count: 2}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{5, 2}

	moves := g.LegalMovesNow()
	if len(moves) != 1 || moves[0].Die != 5 {
		t.Fatalf("должна была быть разрешена только большая кость (5), получено %+v", moves)
	}
}

func TestNoLegalMoveAtAll(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1} // единственная шашка
	g.Board[4] = NardyPoint{Owner: NardyBlack, Count: 2}  // блокирует единственное направление (10-6=4) при дубле 6-6
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{6, 6, 6, 6}

	moves := g.LegalMovesNow()
	if len(moves) != 0 {
		t.Errorf("ожидалось отсутствие легальных ходов, получено %+v", moves)
	}
}

func TestNoLegalMoveAfterRollEndsTurnImmediately(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[10] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.Board[4] = NardyPoint{Owner: NardyBlack, Count: 2}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseRoll

	g.Roll(6, 6)
	if g.Phase != NardyPhaseRoll || g.CurrentPlayer != NardyBlack {
		t.Errorf("при отсутствии ходов очки должны были сгореть, а ход перейти к чёрным сразу; получено Phase=%v CurrentPlayer=%v", g.Phase, g.CurrentPlayer)
	}
}

func TestWinWhenAllCheckersBorneOff(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[0] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.BorneOff[NardyWhite] = 14
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{1}

	if err := g.ApplyMove(NardyMove{From: 0, Die: 1, BearOff: true}); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Phase != NardyPhaseOver || g.Result == nil || g.Result.Winner != NardyWhite {
		t.Fatalf("ожидалась победа белых, получено Phase=%v Result=%+v", g.Phase, g.Result)
	}
}

func TestTurnSwitchesPlayerWhenDiceExhausted(t *testing.T) {
	g := NewNardyGame(NardyShort, NardyWhite)
	g.Board = [24]NardyPoint{}
	g.Board[15] = NardyPoint{Owner: NardyWhite, Count: 1}
	g.CurrentPlayer = NardyWhite
	g.Phase = NardyPhaseMove
	g.DiceLeft = []int{5}

	if err := g.ApplyMove(NardyMove{From: 15, Die: 5}); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.CurrentPlayer != NardyBlack || g.Phase != NardyPhaseRoll {
		t.Errorf("после использования всех костей ход должен был перейти к чёрным (фаза броска), получено CurrentPlayer=%v Phase=%v", g.CurrentPlayer, g.Phase)
	}
}
