package main

import "errors"

// ПРАВИЛА В ЭТОЙ РЕАЛИЗАЦИИ: каждый игрок играет против дилера (не
// друг против друга). Дилер берёт карты, пока сумма меньше 17, и
// останавливается на любых 17 (включая "мягкие" 17 с тузом) — это
// осознанный выбор в пользу простоты и предсказуемости, распростра-
// нённый вариант казино-правил. Сплиты не поддерживаются, удвоение
// ставки — только на первых двух картах.

// BlackjackStatus — состояние руки игрока в текущем раунде.
type BlackjackStatus int

const (
	BJStatusPlaying BlackjackStatus = iota
	BJStatusStood
	BJStatusBusted
	BJStatusBlackjack
)

// BlackjackPlayer — состояние одного игрока за столом.
type BlackjackPlayer struct {
	Name    string
	Hand    []Card
	IsHuman bool
	Balance int
	Bet     int
	Status  BlackjackStatus
}

// BJPhase — фаза текущего раунда.
type BJPhase int

const (
	// BJPhasePlayerTurn — ходит игрок с индексом CurrentPlayerIdx:
	// взять карту, остановиться или удвоить ставку.
	BJPhasePlayerTurn BJPhase = iota
	// BJPhaseRoundOver — раунд завершён, дилер сыграл, выплаты
	// посчитаны.
	BJPhaseRoundOver
)

var (
	ErrBJWrongPhase     = errors.New("недопустимое действие для текущей фазы")
	ErrBJNotEnoughFunds = errors.New("недостаточно средств для этого действия")
	ErrBJCannotDouble   = errors.New("удвоить ставку можно только на первых двух картах")
)

// BlackjackPlayerResult — итог одного игрока в завершившемся раунде.
type BlackjackPlayerResult struct {
	Outcome string // "blackjack", "win", "push", "lose", "bust"
	Delta   int    // изменение баланса за раунд (относительно капитала до ставки)
}

// BlackjackResult — итог завершившегося раунда.
type BlackjackResult struct {
	DealerBusted  bool
	DealerTotal   int
	PlayerResults []BlackjackPlayerResult
}

// BlackjackGameState — полное состояние раунда блэкджека.
type BlackjackGameState struct {
	Players []*BlackjackPlayer
	Dealer  []Card

	Deck *Deck

	CurrentPlayerIdx int
	Phase            BJPhase
	Bet              int // фиксированная базовая ставка за раунд
	StartingCapital  int
	Result           *BlackjackResult

	// RoundBetReduced[i] == true, если у игрока i не хватило баланса
	// на полную ставку Bet в этом раунде — тогда он честно ставит
	// всё, что у него осталось (в том числе 0, если денег нет вовсе),
	// но никогда не уходит в минус.
	RoundBetReduced []bool

	preRoundBalance []int
}

// blackjackCardValue — очковая стоимость карты для блэкджека: туз
// по умолчанию считается за 11 (см. handTotal, где мягкие тузы при
// переборе понижаются до 1), картинки — за 10, остальные — по
// номиналу.
func blackjackCardValue(r Rank) int {
	switch {
	case r == Ace:
		return 11
	case r >= Jack:
		return 10
	default:
		return int(r)
	}
}

// handTotal считает сумму очков руки с учётом мягких тузов: каждый
// туз сначала считается за 11, но при переборе поочерёдно
// понижается до 1, пока сумма не окажется 21 или меньше (либо тузы
// не закончатся).
func handTotal(hand []Card) int {
	total := 0
	aces := 0
	for _, c := range hand {
		total += blackjackCardValue(c.Rank)
		if c.Rank == Ace {
			aces++
		}
	}
	for total > 21 && aces > 0 {
		total -= 10
		aces--
	}
	return total
}

// isNaturalBlackjack проверяет, что рука — блэкджек с раздачи: ровно
// 2 карты и сумма 21 (туз + карта на 10 очков).
func isNaturalBlackjack(hand []Card) bool {
	return len(hand) == 2 && handTotal(hand) == 21
}

// NewBlackjackGame создаёт новый раунд: тасует колоду, списывает
// ставку с каждого игрока, раздаёт по 2 карты игрокам и дилеру,
// сразу отмечает натуральные блэкджеки и передаёт ход первому
// игроку, которому есть что решать.
func NewBlackjackGame(names []string, humanIdx int, startingCapital, bet int) *BlackjackGameState {
	return NewBlackjackRound(names, humanIdx, startingCapital, bet, nil)
}

// NewBlackjackRound — то же самое, что NewBlackjackGame, но
// позволяет передать капитал игроков с предыдущего раунда (balances
// == nil означает "все со стартовым капиталом" — используется при
// самом первом раунде партии).
func NewBlackjackRound(names []string, humanIdx int, startingCapital, bet int, balances []int) *BlackjackGameState {
	deck := NewDeck()
	deck.Shuffle()

	players := make([]*BlackjackPlayer, len(names))
	for i, name := range names {
		balance := startingCapital
		if balances != nil {
			balance = balances[i]
		}
		players[i] = &BlackjackPlayer{
			Name:    name,
			IsHuman: i == humanIdx,
			Balance: balance,
		}
	}

	g := &BlackjackGameState{
		Players:         players,
		Deck:            deck,
		Bet:             bet,
		StartingCapital: startingCapital,
	}

	g.preRoundBalance = make([]int, len(players))
	g.RoundBetReduced = make([]bool, len(players))
	for i, p := range players {
		g.preRoundBalance[i] = p.Balance
		roundBet := bet
		if roundBet > p.Balance {
			roundBet = p.Balance
			g.RoundBetReduced[i] = true
		}
		if roundBet < 0 {
			roundBet = 0
		}
		p.Balance -= roundBet
		p.Bet = roundBet
	}

	// раздача: по 2 карты каждому игроку и дилеру, по одной за проход
	for round := 0; round < 2; round++ {
		for _, p := range g.Players {
			p.Hand = append(p.Hand, g.draw())
		}
		g.Dealer = append(g.Dealer, g.draw())
	}

	for _, p := range g.Players {
		if isNaturalBlackjack(p.Hand) {
			p.Status = BJStatusBlackjack
		}
	}

	g.CurrentPlayerIdx = -1
	g.advanceToNextPlayerOrResolve()
	return g
}

func (g *BlackjackGameState) draw() Card {
	if g.Deck.Len() == 0 {
		// вырожденный случай: даже одна полная колода на 6 игроков
		// не должна кончиться за один раунд, но на всякий случай
		// подмешиваем новую, чтобы не паниковать в рантайме
		fresh := NewDeck()
		fresh.Shuffle()
		g.Deck = fresh
	}
	c, _ := g.Deck.Draw()
	return c
}

func (g *BlackjackGameState) current() *BlackjackPlayer {
	if g.CurrentPlayerIdx < 0 || g.CurrentPlayerIdx >= len(g.Players) {
		return nil
	}
	return g.Players[g.CurrentPlayerIdx]
}

// Hit — текущий игрок берёт ещё одну карту.
func (g *BlackjackGameState) Hit() error {
	if g.Phase != BJPhasePlayerTurn {
		return ErrBJWrongPhase
	}
	p := g.current()
	p.Hand = append(p.Hand, g.draw())
	if handTotal(p.Hand) > 21 {
		p.Status = BJStatusBusted
		g.advanceToNextPlayerOrResolve()
	}
	return nil
}

// Stand — текущий игрок останавливается со своей текущей суммой.
func (g *BlackjackGameState) Stand() error {
	if g.Phase != BJPhasePlayerTurn {
		return ErrBJWrongPhase
	}
	p := g.current()
	p.Status = BJStatusStood
	g.advanceToNextPlayerOrResolve()
	return nil
}

// DoubleDown — удвоить ставку, взять ровно одну карту и
// автоматически остановиться. Доступно только на первых двух картах
// и только если хватает капитала на доплату ставки.
func (g *BlackjackGameState) DoubleDown() error {
	if g.Phase != BJPhasePlayerTurn {
		return ErrBJWrongPhase
	}
	p := g.current()
	if len(p.Hand) != 2 {
		return ErrBJCannotDouble
	}
	if p.Balance < p.Bet {
		return ErrBJNotEnoughFunds
	}
	p.Balance -= p.Bet
	p.Bet *= 2
	p.Hand = append(p.Hand, g.draw())
	if handTotal(p.Hand) > 21 {
		p.Status = BJStatusBusted
	} else {
		p.Status = BJStatusStood
	}
	g.advanceToNextPlayerOrResolve()
	return nil
}

// advanceToNextPlayerOrResolve передаёт ход следующему игроку, у
// которого статус ещё "playing"; если таких не осталось — доигрывает
// дилера и считает выплаты.
func (g *BlackjackGameState) advanceToNextPlayerOrResolve() {
	for idx := g.CurrentPlayerIdx + 1; idx < len(g.Players); idx++ {
		if g.Players[idx].Status == BJStatusPlaying {
			g.CurrentPlayerIdx = idx
			g.Phase = BJPhasePlayerTurn
			return
		}
	}
	g.resolveDealerAndPayouts()
}

// resolveDealerAndPayouts доигрывает руку дилера (берёт карты, пока
// сумма меньше 17, останавливается на любых 17) и рассчитывает
// выплаты каждому игроку.
func (g *BlackjackGameState) resolveDealerAndPayouts() {
	anyoneNeedsDealer := false
	for _, p := range g.Players {
		if p.Status == BJStatusStood || p.Status == BJStatusBlackjack {
			anyoneNeedsDealer = true
			break
		}
	}
	if anyoneNeedsDealer {
		for handTotal(g.Dealer) < 17 {
			g.Dealer = append(g.Dealer, g.draw())
		}
	}

	dealerTotal := handTotal(g.Dealer)
	dealerBusted := dealerTotal > 21
	dealerBlackjack := isNaturalBlackjack(g.Dealer)

	results := make([]BlackjackPlayerResult, len(g.Players))
	for i, p := range g.Players {
		switch p.Status {
		case BJStatusBusted:
			results[i] = BlackjackPlayerResult{Outcome: "bust", Delta: 0}
		case BJStatusBlackjack:
			if dealerBlackjack {
				p.Balance += p.Bet
				results[i] = BlackjackPlayerResult{Outcome: "push"}
			} else {
				payout := p.Bet + p.Bet*3/2
				p.Balance += payout
				results[i] = BlackjackPlayerResult{Outcome: "blackjack"}
			}
		case BJStatusStood:
			playerTotal := handTotal(p.Hand)
			switch {
			case dealerBusted || playerTotal > dealerTotal:
				p.Balance += p.Bet * 2
				results[i] = BlackjackPlayerResult{Outcome: "win"}
			case playerTotal == dealerTotal:
				p.Balance += p.Bet
				results[i] = BlackjackPlayerResult{Outcome: "push"}
			default:
				results[i] = BlackjackPlayerResult{Outcome: "lose"}
			}
		}
		results[i].Delta = p.Balance - g.preRoundBalance[i]
	}

	g.Result = &BlackjackResult{
		DealerBusted:  dealerBusted,
		DealerTotal:   dealerTotal,
		PlayerResults: results,
	}
	g.Phase = BJPhaseRoundOver
}

// NextRound начинает новый раунд, перенося капитал игроков из
// завершившегося раунда.
func (g *BlackjackGameState) NextRound() *BlackjackGameState {
	names := make([]string, len(g.Players))
	balances := make([]int, len(g.Players))
	humanIdx := -1
	for i, p := range g.Players {
		names[i] = p.Name
		balances[i] = p.Balance
		if p.IsHuman {
			humanIdx = i
		}
	}
	return NewBlackjackRound(names, humanIdx, g.StartingCapital, g.Bet, balances)
}
