package main

import "testing"

func TestEvaluate5Categories(t *testing.T) {
	tests := []struct {
		name  string
		cards []Card
		want  PokerHandCategory
	}{
		{"роял-флеш", []Card{c(Ten, Spades), c(Jack, Spades), c(Queen, Spades), c(King, Spades), c(Ace, Spades)}, PokerStraightFlush},
		{"стрит-флеш", []Card{c(Five, Hearts), c(Six, Hearts), c(Seven, Hearts), c(Eight, Hearts), c(Nine, Hearts)}, PokerStraightFlush},
		{"каре", []Card{c(Nine, Clubs), c(Nine, Diamonds), c(Nine, Hearts), c(Nine, Spades), c(Two, Clubs)}, PokerFourOfAKind},
		{"фулл-хаус", []Card{c(King, Clubs), c(King, Diamonds), c(King, Hearts), c(Two, Spades), c(Two, Clubs)}, PokerFullHouse},
		{"флеш", []Card{c(Two, Hearts), c(Five, Hearts), c(Seven, Hearts), c(Nine, Hearts), c(King, Hearts)}, PokerFlush},
		{"стрит", []Card{c(Four, Clubs), c(Five, Diamonds), c(Six, Hearts), c(Seven, Spades), c(Eight, Clubs)}, PokerStraight},
		{"колесо (A-5 стрит)", []Card{c(Ace, Clubs), c(Two, Diamonds), c(Three, Hearts), c(Four, Spades), c(Five, Clubs)}, PokerStraight},
		{"тройка", []Card{c(Jack, Clubs), c(Jack, Diamonds), c(Jack, Hearts), c(Two, Spades), c(Nine, Clubs)}, PokerThreeOfAKind},
		{"две пары", []Card{c(Ten, Clubs), c(Ten, Diamonds), c(Four, Hearts), c(Four, Spades), c(Nine, Clubs)}, PokerTwoPair},
		{"пара", []Card{c(Eight, Clubs), c(Eight, Diamonds), c(Two, Hearts), c(Five, Spades), c(Nine, Clubs)}, PokerPair},
		{"старшая карта", []Card{c(Two, Clubs), c(Five, Diamonds), c(Seven, Hearts), c(Nine, Spades), c(King, Clubs)}, PokerHighCard},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := evaluate5(tt.cards)
			if got.Category != tt.want {
				t.Errorf("ожидалась категория %v, получено %v (карты: %v)", tt.want, got.Category, tt.cards)
			}
		})
	}
}

func TestWheelStraightRanksAsFive(t *testing.T) {
	wheel := evaluate5([]Card{c(Ace, Clubs), c(Two, Diamonds), c(Three, Hearts), c(Four, Spades), c(Five, Clubs)})
	if wheel.Category != PokerStraight || wheel.Tiebreak[0] != 5 {
		t.Fatalf("колесо A-2-3-4-5 должно оцениваться как стрит со старшей картой 5, получено %+v", wheel)
	}
	sixHighStraight := evaluate5([]Card{c(Two, Clubs), c(Three, Diamonds), c(Four, Hearts), c(Five, Spades), c(Six, Clubs)})
	if ComparePokerHands(sixHighStraight, wheel) <= 0 {
		t.Errorf("стрит 2-6 должен быть старше колеса A-5")
	}
}

func TestNotAStraightWhenWheelBrokenByAceHigh(t *testing.T) {
	notStraight := evaluate5([]Card{c(Ace, Clubs), c(Two, Diamonds), c(Three, Hearts), c(Four, Spades), c(King, Clubs)})
	if notStraight.Category == PokerStraight || notStraight.Category == PokerStraightFlush {
		t.Errorf("A,2,3,4,K не должно засчитываться как стрит, получено %v", notStraight.Category)
	}
}

func TestRoyalFlushBeatsStraightFlush(t *testing.T) {
	royal := evaluate5([]Card{c(Ten, Spades), c(Jack, Spades), c(Queen, Spades), c(King, Spades), c(Ace, Spades)})
	lower := evaluate5([]Card{c(Five, Hearts), c(Six, Hearts), c(Seven, Hearts), c(Eight, Hearts), c(Nine, Hearts)})
	if ComparePokerHands(royal, lower) <= 0 {
		t.Errorf("роял-флеш должен быть строго сильнее стрит-флеша 5-9")
	}
}

func TestFourOfAKindKickerBreaksTie(t *testing.T) {
	a := evaluate5([]Card{c(Nine, Clubs), c(Nine, Diamonds), c(Nine, Hearts), c(Nine, Spades), c(King, Clubs)})
	b := evaluate5([]Card{c(Nine, Clubs), c(Nine, Diamonds), c(Nine, Hearts), c(Nine, Spades), c(Two, Clubs)})
	if ComparePokerHands(a, b) <= 0 {
		t.Errorf("одинаковое каре девяток с кикером королём должно побеждать кикер двойку")
	}
}

func TestFullHouseTripsMatterMoreThanPair(t *testing.T) {
	a := evaluate5([]Card{c(Two, Clubs), c(Two, Diamonds), c(Two, Hearts), c(King, Spades), c(King, Clubs)})
	b := evaluate5([]Card{c(Queen, Clubs), c(Queen, Diamonds), c(Queen, Hearts), c(Ace, Spades), c(Ace, Clubs)})
	if ComparePokerHands(b, a) <= 0 {
		t.Errorf("фулл-хаус на тройке дам должен побеждать фулл-хаус на тройке двоек, несмотря на более слабую пару")
	}
}

func TestTwoPairComparesHigherPairFirst(t *testing.T) {
	a := evaluate5([]Card{c(King, Clubs), c(King, Diamonds), c(Two, Hearts), c(Two, Spades), c(Nine, Clubs)})
	b := evaluate5([]Card{c(Queen, Clubs), c(Queen, Diamonds), c(Jack, Hearts), c(Jack, Spades), c(Ace, Clubs)})
	if ComparePokerHands(a, b) <= 0 {
		t.Errorf("две пары королей и двоек должны побеждать пары дам и валетов, несмотря на более слабый кикер")
	}
}

func TestTwoPairKickerBreaksExactTie(t *testing.T) {
	a := evaluate5([]Card{c(King, Clubs), c(King, Diamonds), c(Two, Hearts), c(Two, Spades), c(Ace, Clubs)})
	b := evaluate5([]Card{c(King, Hearts), c(King, Spades), c(Two, Clubs), c(Two, Diamonds), c(Nine, Hearts)})
	if ComparePokerHands(a, b) <= 0 {
		t.Errorf("при равных парах решает кикер — туз должен побеждать девятку")
	}
}

func TestPairComparesKickersInOrder(t *testing.T) {
	a := evaluate5([]Card{c(Eight, Clubs), c(Eight, Diamonds), c(Ace, Hearts), c(Two, Spades), c(Three, Clubs)})
	b := evaluate5([]Card{c(Eight, Hearts), c(Eight, Spades), c(King, Clubs), c(Queen, Diamonds), c(Jack, Hearts)})
	if ComparePokerHands(a, b) <= 0 {
		t.Errorf("при равных парах восьмёрок кикер-туз должен побеждать набор король-дама-валет")
	}
}

func TestFlushComparesAllFiveCardsInOrder(t *testing.T) {
	a := evaluate5([]Card{c(Two, Hearts), c(Five, Hearts), c(Seven, Hearts), c(Nine, Hearts), c(Ace, Hearts)})
	b := evaluate5([]Card{c(Two, Spades), c(Five, Spades), c(Seven, Spades), c(Nine, Spades), c(King, Spades)})
	if ComparePokerHands(a, b) <= 0 {
		t.Errorf("флеш с тузом должен побеждать флеш с королём при прочих равных")
	}
}

func TestIdenticalHandsSplitPot(t *testing.T) {
	a := evaluate5([]Card{c(Two, Hearts), c(Five, Hearts), c(Seven, Hearts), c(Nine, Hearts), c(King, Hearts)})
	b := evaluate5([]Card{c(Two, Spades), c(Five, Spades), c(Seven, Spades), c(Nine, Spades), c(King, Spades)})
	if ComparePokerHands(a, b) != 0 {
		t.Errorf("одинаковые по значению флеши разных мастей должны оцениваться как полностью равные (сплит банка), получено a=%+v b=%+v", a, b)
	}
}

func TestEvaluateBest5ChoosesBestOfSeven(t *testing.T) {
	hole := []Card{c(Ace, Spades), c(Ace, Hearts)}
	board := []Card{c(Ace, Clubs), c(Ace, Diamonds), c(King, Spades), c(Two, Hearts), c(Three, Clubs)}
	all := append(append([]Card{}, hole...), board...)

	best := EvaluateBest5(all)
	if best.Category != PokerFourOfAKind {
		t.Fatalf("ожидалось каре тузов из семи карт, получено %v", best.Category)
	}
	if best.Tiebreak[0] != 14 || best.Tiebreak[1] != 13 {
		t.Errorf("кикером каре тузов должен был стать король (13), получено %+v", best.Tiebreak)
	}
}

func TestEvaluateBest5IgnoresWorseFiveCardSubset(t *testing.T) {
	hole := []Card{c(Two, Hearts), c(Nine, Hearts)}
	board := []Card{c(Five, Hearts), c(Six, Hearts), c(Seven, Clubs), c(Eight, Diamonds), c(King, Hearts)}
	all := append(append([]Card{}, hole...), board...)

	best := EvaluateBest5(all)
	if best.Category != PokerFlush {
		t.Fatalf("ожидался флеш как лучшая комбинация из семи карт, получено %v", best.Category)
	}
}

func TestCombinations5Count(t *testing.T) {
	cards := make([]Card, 7)
	for i := range cards {
		cards[i] = c(Rank(2+i), Clubs)
	}
	count := 0
	combinations5(cards, func(combo []Card) { count++ })
	if count != 21 {
		t.Errorf("ожидалось ровно 21 сочетание из 7 по 5, получено %d", count)
	}
}
