package main

import "testing"

func TestThousandDealsCorrectly(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	for i, p := range g.Players {
		if len(p.Hand) != 7 {
			t.Errorf("игрок %d: ожидалось 7 карт, получено %d", i, len(p.Hand))
		}
	}
	if len(g.Kitty) != 3 {
		t.Fatalf("ожидалось 3 карты в прикупе, получено %d", len(g.Kitty))
	}
	if g.CurrentBidderIdx != (g.DealerIdx+1)%3 {
		t.Errorf("первым в торгах должен ходить игрок слева от сдающего")
	}
}

func TestFirstBidderMustBidAtLeast100(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	if err := g.Pass(); err != ErrThousandMustBid {
		t.Errorf("первый ходящий не может пасовать, ожидалась ErrThousandMustBid, получено: %v", err)
	}
	if err := g.Bid(95); err != ErrThousandBidTooLow {
		t.Errorf("ставка меньше 100 должна быть отклонена, получено: %v", err)
	}
	if err := g.Bid(100); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
}

func TestBidMustBeMultipleOf5AndHigherThanCurrent(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	if err := g.Bid(100); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.Bid(102); err != ErrThousandBidTooLow {
		t.Errorf("ставка не кратная 5 должна быть отклонена, получено: %v", err)
	}
	if err := g.Bid(100); err != ErrThousandBidTooLow {
		t.Errorf("ставка не выше текущей должна быть отклонена, получено: %v", err)
	}
	if err := g.Bid(105); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
}

func TestBidCappedAt120WithoutMarriage(t *testing.T) {
	noMarriageHand := []Card{
		c(Nine, Clubs), c(Jack, Clubs), c(Nine, Hearts),
		c(Jack, Hearts), c(Nine, Diamonds), c(Jack, Diamonds), c(Nine, Spades),
	}

	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	g.Players[g.CurrentBidderIdx].Hand = append([]Card{}, noMarriageHand...)
	if err := g.Bid(120); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}

	// проверяем превышение кэпа отдельной игрой — чтобы ставка 125
	// проверялась для ТОГО ЖЕ игрока с той же рукой, а не для
	// следующего (ход уже передан после успешной ставки выше)
	g2 := NewThousandGame([]string{"A", "B", "C"}, -1)
	g2.Players[g2.CurrentBidderIdx].Hand = append([]Card{}, noMarriageHand...)
	if err := g2.Bid(125); err != ErrThousandBidTooHigh {
		t.Errorf("без марьяжа ставка выше 120 должна быть отклонена, получено: %v", err)
	}
}

func TestBidCapRaisedByMarriage(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	g.Players[g.CurrentBidderIdx].Hand = []Card{
		c(King, Hearts), c(Queen, Hearts), c(Nine, Clubs),
		c(Nine, Diamonds), c(Nine, Spades), c(Jack, Clubs), c(Jack, Diamonds),
	}
	if err := g.Bid(220); err != nil {
		t.Fatalf("ставка 120+100 должна быть разрешена при червовом марьяже: %v", err)
	}
	g2 := NewThousandGame([]string{"A", "B", "C"}, -1)
	g2.Players[g2.CurrentBidderIdx].Hand = []Card{
		c(King, Hearts), c(Queen, Hearts), c(Nine, Clubs),
		c(Nine, Diamonds), c(Nine, Spades), c(Jack, Clubs), c(Jack, Diamonds),
	}
	if err := g2.Bid(225); err != ErrThousandBidTooHigh {
		t.Errorf("ставка выше 220 должна быть отклонена, получено: %v", err)
	}
}

func TestBiddingEndsWhenOneBidderRemains(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	first := g.CurrentBidderIdx
	if err := g.Bid(100); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.Pass(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.Pass(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Phase != ThousandPhaseDiscard {
		t.Fatalf("торги должны были завершиться после 2 пасов, фаза: %v", g.Phase)
	}
	if g.DeclarerIdx != first {
		t.Errorf("объявившим торг должен стать %d, получено %d", first, g.DeclarerIdx)
	}
	if g.FinalBid != 100 {
		t.Errorf("ожидалась итоговая ставка 100, получено %d", g.FinalBid)
	}
	if len(g.Players[first].Hand) != 10 {
		t.Errorf("объявивший торг должен был получить прикуп (10 карт), получено %d", len(g.Players[first].Hand))
	}
}

func TestDiscardGivesCardsToOpponentsAndStartsTrick(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	if err := g.Bid(100); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.Pass(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.Pass(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	declarer := g.DeclarerIdx
	hand := append([]Card{}, g.Players[declarer].Hand...)

	if err := g.DiscardCard(hand[0]); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Phase != ThousandPhaseDiscard {
		t.Fatalf("после первого сброса раздача ещё не должна была начаться")
	}
	if err := g.DiscardCard(hand[1]); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Phase != ThousandPhaseTrick {
		t.Fatalf("после второго сброса должна была начаться игра взятками")
	}
	if len(g.Players[declarer].Hand) != 8 {
		t.Errorf("у объявившего торг должно остаться 8 карт, получено %d", len(g.Players[declarer].Hand))
	}
	for i, p := range g.Players {
		if i == declarer {
			continue
		}
		if len(p.Hand) != 8 {
			t.Errorf("у игрока %d должно стать 8 карт после получения снoса, получено %d", i, len(p.Hand))
		}
	}
	if g.LeaderIdx != declarer {
		t.Errorf("объявивший торг должен ходить первым")
	}
}

// setupThousandTrickPlay — тестовый хелпер: пропускает торги и снос,
// вручную задаёт руки игроков и переводит игру сразу в фазу взятки.
func setupThousandTrickPlay(g *ThousandGameState, declarerIdx int, hands map[int][]Card) {
	g.DeclarerIdx = declarerIdx
	g.FinalBid = 100
	for idx, hand := range hands {
		g.Players[idx].Hand = hand
		g.Players[idx].HandPoints = 0
	}
	g.LeaderIdx = declarerIdx
	g.CurrentTrick = nil
	g.TricksPlayed = 0
	g.TrumpSet = false
	g.Phase = ThousandPhaseTrick
}

func TestPlayCardFollowSuitRequired(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	setupThousandTrickPlay(g, 0, map[int][]Card{
		0: {c(Nine, Clubs)},
		1: {c(Nine, Hearts), c(Jack, Clubs)},
		2: {c(Nine, Spades)},
	})
	if err := g.PlayCard(c(Nine, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Nine, Hearts)); err != ErrThousandInvalidFollow {
		t.Errorf("ожидалась ошибка обязательной масти, получено: %v", err)
	}
	if err := g.PlayCard(c(Jack, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка при верном ходе в масть: %v", err)
	}
}

func TestPlayCardMustTrumpWhenNoLedSuit(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	setupThousandTrickPlay(g, 0, map[int][]Card{
		0: {c(Nine, Clubs)},
		1: {c(Jack, Hearts), c(Nine, Spades)},
		2: {c(Nine, Diamonds)},
	})
	g.TrumpSet = true
	g.TrumpSuit = Hearts

	if err := g.PlayCard(c(Nine, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Nine, Spades)); err != ErrThousandInvalidFollow {
		t.Errorf("при отсутствии масти, но наличии козыря, обязан ходить козырем, получено: %v", err)
	}
	if err := g.PlayCard(c(Jack, Hearts)); err != nil {
		t.Fatalf("неожиданная ошибка при ходе козырем: %v", err)
	}
}

func TestTrickWinnerHighestOfLedSuit(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	setupThousandTrickPlay(g, 0, map[int][]Card{
		0: {c(Jack, Clubs)},
		1: {c(King, Clubs)},
		2: {c(Nine, Clubs)},
	})
	if err := g.PlayCard(c(Jack, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(King, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Nine, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.LeaderIdx != 1 {
		t.Errorf("ожидался победитель взятки — игрок 1 (король треф), получено %d", g.LeaderIdx)
	}
	if g.Players[1].HandPoints != 6 {
		t.Errorf("ожидалось 6 очков за взятку, получено %d", g.Players[1].HandPoints)
	}
}

func TestTenBeatsKingButLosesToAce(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	setupThousandTrickPlay(g, 0, map[int][]Card{
		0: {c(King, Clubs)},
		1: {c(Ten, Clubs)},
		2: {c(Ace, Clubs)},
	})
	if err := g.PlayCard(c(King, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Ten, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Ace, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.LeaderIdx != 2 {
		t.Errorf("туз должен побить и десятку, и короля; ожидался победитель 2, получено %d", g.LeaderIdx)
	}
}

func TestTrumpBeatsNonTrumpEvenIfLower(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	setupThousandTrickPlay(g, 0, map[int][]Card{
		0: {c(Ace, Clubs)},
		1: {c(Nine, Hearts), c(Nine, Spades)},
		2: {c(Queen, Diamonds), c(Nine, Diamonds)},
	})
	g.TrumpSet = true
	g.TrumpSuit = Hearts

	if err := g.PlayCard(c(Ace, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Nine, Hearts)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Nine, Diamonds)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.LeaderIdx != 1 {
		t.Errorf("младший козырь должен побить туза не в масть, ожидался победитель 1, получено %d", g.LeaderIdx)
	}
}

func TestMarriageDeclarationSetsTrumpAndScores(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	setupThousandTrickPlay(g, 0, map[int][]Card{
		0: {c(Nine, Clubs), c(King, Diamonds), c(Queen, Diamonds)},
		1: {c(Jack, Clubs)},
		2: {c(Ten, Clubs)},
	})
	if err := g.PlayCard(c(Nine, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Jack, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.PlayCard(c(Ten, Clubs)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.TricksPlayed != 1 {
		t.Fatalf("ожидалась 1 сыгранная взятка, получено %d", g.TricksPlayed)
	}
	winner := g.LeaderIdx

	if winner == 0 {
		if err := g.PlayCard(c(King, Diamonds)); err != nil {
			t.Fatalf("неожиданная ошибка объявления марьяжа: %v", err)
		}
		if !g.TrumpSet || g.TrumpSuit != Diamonds {
			t.Fatalf("бубновый марьяж должен был назначить козырь бубны")
		}
		if g.Players[0].HandPoints < 80 {
			t.Errorf("ожидались очки марьяжа (80) в счёте игрока 0, получено %d", g.Players[0].HandPoints)
		}
	}
}

func TestMarriageCannotBeDeclaredOnFirstTrick(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	setupThousandTrickPlay(g, 0, map[int][]Card{
		0: {c(King, Diamonds), c(Queen, Diamonds), c(Nine, Clubs)},
		1: {c(Nine, Hearts)},
		2: {c(Nine, Spades)},
	})
	if err := g.PlayCard(c(King, Diamonds)); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.TrumpSet {
		t.Errorf("марьяж не должен объявляться на самой первой взятке раздачи")
	}
}

func TestHandScoringDeclarerMakesBid(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	g.DeclarerIdx = 0
	g.FinalBid = 100
	g.Players[0].HandPoints = 110
	g.Players[1].HandPoints = 5
	g.Players[2].HandPoints = 5
	g.resolveHandEnd()

	if !g.Result.Made {
		t.Fatalf("объявивший торг должен был выполнить ставку")
	}
	if g.Result.ScoreDeltas[0] != 100 {
		t.Errorf("выполнившему ставку засчитывается сама ставка (100), получено %d", g.Result.ScoreDeltas[0])
	}
	if g.Players[0].Score != 100 {
		t.Errorf("ожидался счёт 100, получено %d", g.Players[0].Score)
	}
}

func TestHandScoringDeclarerFailsBid(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	g.DeclarerIdx = 0
	g.FinalBid = 100
	g.Players[0].HandPoints = 60
	g.Players[1].HandPoints = 40
	g.Players[2].HandPoints = 20
	g.resolveHandEnd()

	if g.Result.Made {
		t.Fatalf("не должен был выполнить ставку")
	}
	if g.Result.ScoreDeltas[0] != -100 {
		t.Errorf("невыполнившему ставку списывается ставка (-100), получено %d", g.Result.ScoreDeltas[0])
	}
	if g.Result.ScoreDeltas[1] != 40 || g.Result.ScoreDeltas[2] != 20 {
		t.Errorf("соперники должны получить ровно свои очки: %v", g.Result.ScoreDeltas)
	}
}

func TestMatchOverAt1000(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	g.Players[0].Score = 920
	g.DeclarerIdx = 0
	g.FinalBid = 100
	g.Players[0].HandPoints = 100
	g.Players[1].HandPoints = 0
	g.Players[2].HandPoints = 0
	g.resolveHandEnd()

	if !g.Result.MatchOver {
		t.Fatalf("партия должна была завершиться при достижении 1000 очков")
	}
	if g.Result.MatchWinner != 0 {
		t.Errorf("ожидался победитель 0, получено %d", g.Result.MatchWinner)
	}
}

func TestNextHandRotatesDealer(t *testing.T) {
	g := NewThousandGame([]string{"A", "B", "C"}, -1)
	oldDealer := g.DealerIdx
	g.NextHand()
	if g.DealerIdx != (oldDealer+1)%3 {
		t.Errorf("сдающий должен был смениться по кругу")
	}
	if g.Phase != ThousandPhaseBidding {
		t.Errorf("новая раздача должна была начаться с торгов")
	}
}
