package blackjack

import (
	"encoding/json"
	"testing"

	"pete/internal/games/cards"
)

// Split is the only move in blackjack that takes chips out of a player's stack
// after the cards are already out, so most of what can go wrong with it is money
// rather than cards. These tests are mostly about the money.

// dealt builds a state mid-hand: the player holding `player`, the dealer showing
// `dealer`, and a shoe stacked with `shoe` so the next cards are known. It skips
// New() because a split needs a pair, and waiting for one out of a shuffled shoe
// is not a test, it's a slot machine.
func dealt(bet int64, player, dealer, shoe []cards.Card) State {
	return State{
		Rules:  DefaultRules(),
		Deck:   cards.Deck(shoe),
		Dealer: dealer,
		Hands:  []Hand{{Cards: player, Bet: bet}},
		Bet:    bet,
		Phase:  PhasePlayer,
	}
}

func TestSplitDealsTwoHandsAndTakesASecondBet(t *testing.T) {
	s := dealt(100,
		hand(8, 8),
		hand(cards.King, 6),
		hand(3, 9, 5, 5), // one card to each hand, then whatever the dealer needs
	)
	if !s.CanSplit() {
		t.Fatal("CanSplit says no to a pair of eights")
	}
	if s.SplitCost() != 100 {
		t.Fatalf("SplitCost = %d, want the same bet again (100)", s.SplitCost())
	}

	s, evs, err := ApplyMove(s, Split)
	if err != nil {
		t.Fatal(err)
	}
	if len(s.Hands) != 2 {
		t.Fatalf("split made %d hands, want 2", len(s.Hands))
	}
	for i, h := range s.Hands {
		if len(h.Cards) != 2 {
			t.Errorf("hand %d holds %d cards after the split, want 2", i, len(h.Cards))
		}
		if h.Cards[0].Rank != 8 {
			t.Errorf("hand %d didn't keep an eight: %s", i, cards.Hand(h.Cards))
		}
		if h.Bet != 100 {
			t.Errorf("hand %d carries a bet of %d, want 100", i, h.Bet)
		}
		if !h.Split {
			t.Errorf("hand %d isn't marked as split", i)
		}
	}
	// The whole point, in one line: there is twice as much on the table as there
	// was, and the shell has to have taken it.
	if s.Bet != 200 {
		t.Fatalf("total stake = %d after splitting a 100 hand, want 200", s.Bet)
	}
	if s.Active != 0 {
		t.Fatalf("active hand = %d, want the left one first", s.Active)
	}

	var split, cardsDealt int
	for _, e := range evs {
		switch e.Kind {
		case "split":
			split++
		case "player_card":
			cardsDealt++
		}
	}
	if split != 1 || cardsDealt != 2 {
		t.Fatalf("the split emitted %d split and %d card events, want 1 and 2", split, cardsDealt)
	}
}

// Split aces get one card each and no further say. Without that rule a pair of
// aces is the best hand in the game every single time.
func TestSplitAcesGetOneCardEachAndNoMore(t *testing.T) {
	s := dealt(50,
		hand(cards.Ace, cards.Ace),
		hand(9, 7), // dealer sits on 16, has to draw
		hand(cards.King, 9, 5),
	)
	s, _, err := ApplyMove(s, Split)
	if err != nil {
		t.Fatal(err)
	}
	for i, h := range s.Hands {
		if len(h.Cards) != 2 {
			t.Errorf("split ace %d holds %d cards, want exactly 2", i, len(h.Cards))
		}
		if !h.Done {
			t.Errorf("split ace %d is still being asked for a decision", i)
		}
	}
	// Both hands are finished the moment they're dealt, so the dealer plays and
	// the deal settles without the player ever acting again.
	if s.Phase != PhaseDone {
		t.Fatalf("phase = %q after splitting aces, want the deal to have run to the end", s.Phase)
	}
}

// An ace and a ten on a split hand is twenty-one. It is not a blackjack, and the
// house does not pay 3:2 for it — which is the single most expensive rule in this
// file, because splitting aces makes 21s for a living.
func TestTwentyOneOnASplitHandIsNotANatural(t *testing.T) {
	s := dealt(100,
		hand(cards.Ace, cards.Ace),
		hand(cards.King, 7), // dealer stands on 17
		hand(cards.King, cards.Queen),
	)
	s, _, err := ApplyMove(s, Split)
	if err != nil {
		t.Fatal(err)
	}
	if s.Phase != PhaseDone {
		t.Fatalf("phase = %q, want done", s.Phase)
	}
	for i, h := range s.Hands {
		if v, _ := h.Value(); v != 21 {
			t.Fatalf("hand %d is %d, want the 21 this test is about", i, v)
		}
		if h.Natural() {
			t.Errorf("hand %d counts as a natural — a split 21 must not pay 3:2", i)
		}
		if h.Outcome != OutcomeWin {
			t.Errorf("hand %d settled as %q, want a plain win", i, h.Outcome)
		}
		// 100 staked, 100 profit, 5% rake off the profit: 195 back, not 245.
		if h.Payout != 195 {
			t.Errorf("hand %d paid %d, want 195 (a 1:1 win less the rake), not a 3:2 payout", i, h.Payout)
		}
	}
	if s.Payout != 390 {
		t.Fatalf("the deal paid %d, want 390", s.Payout)
	}
}

// Same rank, not same value. A king and a queen are both worth ten and are not a
// pair.
func TestSplitNeedsAPairOfTheSameRank(t *testing.T) {
	s := dealt(100, hand(cards.King, cards.Queen), hand(9, 9), hand(5))
	if s.CanSplit() {
		t.Error("CanSplit says a king and a queen are a pair")
	}
	if _, _, err := ApplyMove(s, Split); err != ErrCantSplit {
		t.Errorf("splitting K+Q gave %v, want ErrCantSplit", err)
	}

	// And you cannot split a hand you have already hit.
	s = dealt(100, hand(8, 8, 5), hand(9, 9), hand(5))
	if s.CanSplit() {
		t.Error("CanSplit says yes to a three-card hand")
	}
}

func TestSplittingStopsAtFourHands(t *testing.T) {
	// Every card an eight, so every hand splits again for as long as it's allowed.
	shoe := make([]cards.Card, 0, 12)
	for i := 0; i < 12; i++ {
		shoe = append(shoe, cards.Card{Rank: 8, Suit: cards.Spades})
	}
	s := dealt(100, hand(8, 8), hand(9, 7), shoe)

	for i := 0; i < MaxHands-1; i++ {
		var err error
		if s, _, err = ApplyMove(s, Split); err != nil {
			t.Fatalf("split %d: %v", i+1, err)
		}
	}
	if len(s.Hands) != MaxHands {
		t.Fatalf("ended with %d hands, want %d", len(s.Hands), MaxHands)
	}
	if s.CanSplit() {
		t.Fatalf("CanSplit says yes at %d hands", MaxHands)
	}
	if _, _, err := ApplyMove(s, Split); err != ErrCantSplit {
		t.Errorf("the fifth split gave %v, want ErrCantSplit", err)
	}
	if s.Bet != 400 {
		t.Errorf("four hands of 100 = %d staked, want 400", s.Bet)
	}
}

// Each hand is raked on its own winnings. Netting the hands against each other
// first would mean a player who wins one and loses one pays no rake at all, which
// is not a rake, it's a discount for splitting.
func TestEachSplitHandIsSettledAndRakedOnItsOwn(t *testing.T) {
	s := dealt(100,
		hand(8, 8),
		hand(cards.King, 9), // dealer stands on 19
		// left hand gets a 2 (10, then it hits to 20 and stands);
		// right hand gets a 3 (11) and will bust on a king.
		hand(2, 3, cards.King, cards.King),
	)
	s, _, err := ApplyMove(s, Split)
	if err != nil {
		t.Fatal(err)
	}
	if s, _, err = ApplyMove(s, Hit); err != nil { // left: 8+2+K = 20
		t.Fatal(err)
	}
	if v, _ := s.Hands[0].Value(); v != 20 {
		t.Fatalf("left hand is %d, want 20", v)
	}
	if s, _, err = ApplyMove(s, Stand); err != nil {
		t.Fatal(err)
	}
	if s.Active != 1 {
		t.Fatalf("standing on the left hand left the active hand at %d, want 1", s.Active)
	}
	if s, _, err = ApplyMove(s, Hit); err != nil { // right: 8+3+K = 21... no: 8+3=11, +K = 21
		t.Fatal(err)
	}
	if s.Phase != PhaseDone {
		t.Fatalf("phase = %q, want done", s.Phase)
	}

	left, right := s.Hands[0], s.Hands[1]
	if left.Outcome != OutcomeWin { // 20 beats 19
		t.Errorf("left hand settled %q, want a win", left.Outcome)
	}
	if right.Outcome != OutcomeWin { // 21 beats 19
		t.Errorf("right hand settled %q, want a win", right.Outcome)
	}
	// Two 100 hands, each winning 100, each raked 5 on its own profit.
	if left.Rake != 5 || right.Rake != 5 || s.Rake != 10 {
		t.Errorf("rake = %d/%d (total %d), want 5/5 (10) — each hand raked on its own winnings", left.Rake, right.Rake, s.Rake)
	}
	if s.Payout != 390 || s.Net() != 190 {
		t.Errorf("payout = %d net = %d, want 390 and 190", s.Payout, s.Net())
	}
}

// A hand that busts loses its own bet and nothing else. The other hand is a
// separate bet and settles on its own merits.
func TestBustingOneSplitHandDoesNotTouchTheOther(t *testing.T) {
	s := dealt(100,
		hand(9, 9),
		hand(cards.King, 8), // dealer stands on 18
		hand(5, 2, cards.King, cards.King),
	)
	s, _, err := ApplyMove(s, Split) // left: 9+5=14, right: 9+2=11
	if err != nil {
		t.Fatal(err)
	}
	if s, _, err = ApplyMove(s, Hit); err != nil { // left: 14+K = 24, bust
		t.Fatal(err)
	}
	if s.Hands[0].Outcome != "" && s.Hands[0].Outcome != OutcomeBust {
		t.Fatalf("left hand outcome %q before settling", s.Hands[0].Outcome)
	}
	if !s.Hands[0].Done || s.Active != 1 {
		t.Fatalf("a bust hand didn't hand over: done=%v active=%d", s.Hands[0].Done, s.Active)
	}
	if s, _, err = ApplyMove(s, Hit); err != nil { // right: 11+K = 21
		t.Fatal(err)
	}

	if s.Hands[0].Outcome != OutcomeBust || s.Hands[0].Payout != 0 {
		t.Errorf("left hand = %q paying %d, want a bust paying nothing", s.Hands[0].Outcome, s.Hands[0].Payout)
	}
	if s.Hands[1].Outcome != OutcomeWin || s.Hands[1].Payout != 195 {
		t.Errorf("right hand = %q paying %d, want a win paying 195", s.Hands[1].Outcome, s.Hands[1].Payout)
	}
	// Staked 200, got 195 back: down 5 on the deal, and the word for that is lose.
	if s.Payout != 195 || s.Net() != -5 || s.Outcome != OutcomeLose {
		t.Errorf("deal settled %q paying %d (net %d), want lose/195/-5", s.Outcome, s.Payout, s.Net())
	}
}

// If every hand busts there is nothing left to beat, and the dealer does not turn
// over — same as it always was with one hand.
func TestTheDealerDoesNotPlayWhenEveryHandIsBust(t *testing.T) {
	s := dealt(100,
		hand(9, 9),
		hand(cards.King, 6),
		hand(8, 8, cards.King, cards.King, 4), // both hands reach 17 then bust on a king
	)
	s, _, err := ApplyMove(s, Split)
	if err != nil {
		t.Fatal(err)
	}
	if s, _, err = ApplyMove(s, Hit); err != nil { // left 9+8+K = 27
		t.Fatal(err)
	}
	if s, _, err = ApplyMove(s, Hit); err != nil { // right 9+8+K = 27
		t.Fatal(err)
	}
	if s.Phase != PhaseDone {
		t.Fatalf("phase = %q, want done", s.Phase)
	}
	if len(s.Dealer) != 2 {
		t.Errorf("the dealer drew to %d cards with every hand already bust", len(s.Dealer))
	}
	if s.Payout != 0 {
		t.Errorf("two bust hands paid %d, want nothing", s.Payout)
	}
}

// The dealer not drawing is not the same as the dealer not turning over. The
// browser has been showing a face-down card since the deal, and the settled state
// it gets handed has the dealer's whole total on it — so a bust-out still owes it
// a reveal, or the felt prints a nineteen under a card nobody has looked at.
func TestBustingOutStillTurnsTheHoleCardOver(t *testing.T) {
	s := dealt(100,
		hand(cards.King, 6),
		hand(cards.King, 9),
		hand(cards.King), // 16 + K = 26
	)
	s, evs, err := ApplyMove(s, Hit)
	if err != nil {
		t.Fatal(err)
	}
	if s.Phase != PhaseDone || s.Outcome != OutcomeBust {
		t.Fatalf("phase/outcome = %q/%q, want done/bust", s.Phase, s.Outcome)
	}
	var reveal bool
	for _, e := range evs {
		if e.Kind == "reveal" {
			reveal = true
		}
	}
	if !reveal {
		t.Error("the player busted out and the hole card was never revealed")
	}
}

// Doubling after a split doubles *that hand's* bet, not the whole table's.
func TestDoublingAfterASplitOnlyDoublesThatHand(t *testing.T) {
	s := dealt(100,
		hand(5, 5),
		hand(cards.King, 7),
		hand(6, 4, 9, cards.King),
	)
	s, _, err := ApplyMove(s, Split) // left: 5+6=11, right: 5+4=9
	if err != nil {
		t.Fatal(err)
	}
	if s.DoubleCost() != 100 {
		t.Fatalf("DoubleCost = %d on a split hand of 100, want 100", s.DoubleCost())
	}
	s, _, err = ApplyMove(s, Double) // left doubles: 11 + 9 = 20
	if err != nil {
		t.Fatal(err)
	}
	if s.Hands[0].Bet != 200 || s.Hands[1].Bet != 100 {
		t.Fatalf("bets are %d/%d after doubling the left hand, want 200/100", s.Hands[0].Bet, s.Hands[1].Bet)
	}
	if s.Bet != 300 {
		t.Fatalf("total staked = %d, want 300 (100 + a doubled 100 + 100)", s.Bet)
	}
	if !s.Hands[0].Done || s.Active != 1 {
		t.Fatal("a doubled hand takes one card and hands over")
	}
}

// A live hand outlives a deploy. The blobs written before split existed have a
// "player" array and no "hands", and a state that decodes to no hands at all is a
// player whose cards vanished mid-deal.
func TestALiveHandDealtBeforeSplitExistedStillLoads(t *testing.T) {
	legacy := []byte(`{
		"rules": {"decks": 6, "blackjack_pays": 1.5, "dealer_hits_soft17": true, "rake_pct": 0.05},
		"deck": [],
		"player": [{"r": 10, "s": 1}, {"r": 7, "s": 2}],
		"dealer": [{"r": 9, "s": 0}, {"r": 5, "s": 3}],
		"bet": 250,
		"doubled": false,
		"phase": "player"
	}`)

	var s State
	if err := json.Unmarshal(legacy, &s); err != nil {
		t.Fatal(err)
	}
	if len(s.Hands) != 1 {
		t.Fatalf("an old live hand decoded to %d hands, want 1 — the player's cards went missing", len(s.Hands))
	}
	if len(s.Hands[0].Cards) != 2 {
		t.Fatalf("the revived hand holds %d cards, want 2", len(s.Hands[0].Cards))
	}
	if s.Hands[0].Bet != 250 || s.Bet != 250 {
		t.Fatalf("the revived hand carries %d of the %d staked, want all of it", s.Hands[0].Bet, s.Bet)
	}
	if v, _ := s.Hands[0].Value(); v != 17 {
		t.Fatalf("the revived hand is worth %d, want 17", v)
	}
	// And it can still be played to the end.
	if _, _, err := ApplyMove(s, Stand); err != nil {
		t.Fatalf("the revived hand could not be stood on: %v", err)
	}
}
