package main

import (
	"fmt"
	"math/rand"
	"strings"
	"time"

	tea "github.com/charmbracelet/bubbletea"
)

// PokerModel — TUI поверх PokerGameState. Человек всегда занимает
// место 0; каждому боту при создании партии случайно достаётся один
// из трёх уровней сложности — выбирать сложность игроку не
// предлагается специально (так и было задумано).
type PokerModel struct {
	game      *PokerGameState
	bots      map[int]PokerBot
	humanSeat int
	rng       *rand.Rand

	choosingBetAmount bool
	betAmount         int

	message string
	isError bool

	quitting   bool
	backToMenu bool
}

const (
	pokerStartingStack = 1000
	pokerSmallBlind    = 10
	pokerBigBlind      = 20
)

// NewPokerModel начинает новый турнир на numPlayers мест. Человек —
// место 0; остальным местам случайно назначается один из трёх
// уровней сложности бота.
func NewPokerModel(numPlayers int) PokerModel {
	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	game := NewPokerGame(numPlayers, pokerStartingStack, pokerSmallBlind, pokerBigBlind, rng)
	bots := map[int]PokerBot{}
	for i := 1; i < numPlayers; i++ {
		bots[i] = PokerBot{Difficulty: PokerDifficulty(rng.Intn(3))}
	}
	return PokerModel{game: game, bots: bots, humanSeat: 0, rng: rng}
}

func (m PokerModel) Init() tea.Cmd {
	return m.maybeScheduleBot()
}

func (m PokerModel) maybeScheduleBot() tea.Cmd {
	g := m.game
	if g.Phase == PokerPhaseTournamentOver || g.Phase == PokerPhaseShowdown {
		return nil
	}
	if g.CurrentPlayerIdx == m.humanSeat {
		return nil
	}
	return tea.Tick(botMoveDelay, func(time.Time) tea.Msg { return botMoveMsg{} })
}

func (m *PokerModel) setInfo(key string) {
	m.message = T(key)
	m.isError = false
}

func (m *PokerModel) setError(err error) {
	m.message = err.Error()
	m.isError = true
}

func (m PokerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case botMoveMsg:
		return m.handleBotMove()
	case tea.KeyMsg:
		return m.handleKey(msg)
	}
	return m, nil
}

func (m PokerModel) handleBotMove() (tea.Model, tea.Cmd) {
	g := m.game
	if g.Phase == PokerPhaseTournamentOver || g.Phase == PokerPhaseShowdown {
		return m, nil
	}
	if g.CurrentPlayerIdx == m.humanSeat {
		return m, nil
	}
	seat := g.CurrentPlayerIdx
	bot := m.bots[seat]
	if err := bot.PlayFullTurn(g, seat, m.rng); err != nil {
		m.setError(err)
		return m, nil
	}
	m.message = ""
	return m, m.maybeScheduleBot()
}

// pokerHumanActions строит пронумерованный список действий,
// доступных человеку прямо сейчас.
func (m PokerModel) pokerHumanActions() []string {
	g := m.game
	p := g.Players[m.humanSeat]
	toCall := g.highestBet() - p.CurrentBet
	var actions []string
	actions = append(actions, "fold")
	if toCall == 0 {
		actions = append(actions, "check")
	} else {
		actions = append(actions, "call")
	}
	if p.Stack > 0 {
		actions = append(actions, "bet")
	}
	actions = append(actions, "allin")
	return actions
}

func (m PokerModel) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	key := msg.String()
	if key == "ctrl+c" {
		m.quitting = true
		return m, tea.Quit
	}
	if key == "q" {
		m.backToMenu = true
		return m, nil
	}

	g := m.game
	if g.Phase == PokerPhaseTournamentOver {
		return m, nil
	}

	if g.Phase == PokerPhaseShowdown {
		if key == "n" || key == "enter" {
			g.NextHand(m.rng)
			m.message = ""
			return m, m.maybeScheduleBot()
		}
		return m, nil
	}

	if g.CurrentPlayerIdx != m.humanSeat {
		return m, nil
	}

	if m.choosingBetAmount {
		return m.handleBetAmountKey(key)
	}

	actions := m.pokerHumanActions()
	for i, a := range actions {
		if key != fmt.Sprintf("%d", i+1) {
			continue
		}
		return m.applyActionLabel(a)
	}
	return m, nil
}

func (m PokerModel) applyActionLabel(label string) (tea.Model, tea.Cmd) {
	g := m.game
	p := g.Players[m.humanSeat]
	switch label {
	case "fold":
		if err := g.Fold(); err != nil {
			m.setError(err)
			return m, nil
		}
	case "check":
		if err := g.Check(); err != nil {
			m.setError(err)
			return m, nil
		}
	case "call":
		if err := g.Call(); err != nil {
			m.setError(err)
			return m, nil
		}
	case "bet":
		m.choosingBetAmount = true
		minTotal := g.highestBet() + g.BigBlind
		if g.highestBet() == 0 {
			minTotal = g.BigBlind
		}
		maxTotal := p.CurrentBet + p.Stack
		if minTotal > maxTotal {
			minTotal = maxTotal
		}
		m.betAmount = minTotal
		return m, nil
	case "allin":
		if err := g.AllIn(); err != nil {
			m.setError(err)
			return m, nil
		}
	}
	m.message = ""
	if g.Phase == PokerPhaseShowdown || g.Phase == PokerPhaseTournamentOver {
		return m, nil
	}
	return m, m.maybeScheduleBot()
}

func (m PokerModel) handleBetAmountKey(key string) (tea.Model, tea.Cmd) {
	g := m.game
	p := g.Players[m.humanSeat]
	maxTotal := p.CurrentBet + p.Stack
	minTotal := g.highestBet() + g.BigBlind
	if g.highestBet() == 0 {
		minTotal = g.BigBlind
	}
	if minTotal > maxTotal {
		minTotal = maxTotal
	}

	switch key {
	case "esc":
		m.choosingBetAmount = false
		return m, nil
	case "up", "k":
		m.betAmount += g.BigBlind
	case "down", "j":
		m.betAmount -= g.BigBlind
	case "right", "l":
		m.betAmount += g.BigBlind * 5
	case "left", "h":
		m.betAmount -= g.BigBlind * 5
	case "enter":
		if err := g.Bet(m.betAmount); err != nil {
			m.setError(err)
			return m, nil
		}
		m.choosingBetAmount = false
		m.message = ""
		return m, m.maybeScheduleBot()
	default:
		return m, nil
	}
	if m.betAmount < minTotal {
		m.betAmount = minTotal
	}
	if m.betAmount > maxTotal {
		m.betAmount = maxTotal
	}
	return m, nil
}

func pokerCardStr(c Card) string {
	return c.String()
}

// renderPokerBoardRow рисует общие карты стола: уже открытые —
// обычной рамкой, ещё не сданные (до 5 позиций) — рубашкой вниз, с
// той же шириной, чтобы ряд не "прыгал" по мере вскрытия карт.
func renderPokerBoardRow(board []Card) string {
	boxes := make([][]string, 5)
	for i := 0; i < 5; i++ {
		if i < len(board) {
			boxes[i] = renderCardBox(board[i], CardNormal)
		} else {
			boxes[i] = renderFaceDownBox()
		}
	}
	lines := make([]string, 4)
	for row := 0; row < 4; row++ {
		parts := make([]string, 5)
		for i := 0; i < 5; i++ {
			parts[i] = boxes[i][row]
		}
		lines[row] = strings.Join(parts, " ")
	}
	return strings.Join(lines, "\n")
}

func (m PokerModel) View() string {
	if m.quitting {
		return T("common.goodbye")
	}
	var b strings.Builder
	fmt.Fprintln(&b, titleStyle.Render(T("poker.title")))
	fmt.Fprintln(&b)

	g := m.game
	if g.Phase == PokerPhaseTournamentOver {
		fmt.Fprintln(&b, m.renderTournamentOver())
		fmt.Fprintln(&b)
		fmt.Fprintln(&b, dimStyle.Render(T("poker.hint.quit")))
		return b.String()
	}

	var board []Card
	board = append(board, g.Board...)
	fmt.Fprintln(&b, T("poker.board_label"))
	fmt.Fprintln(&b, renderPokerBoardRow(board))
	fmt.Fprintln(&b, Tf("poker.pot_line", g.potSize()))
	fmt.Fprintln(&b)

	for i, p := range g.Players {
		fmt.Fprintln(&b, m.renderPlayerLine(i, p))
	}
	fmt.Fprintln(&b)

	human := g.Players[m.humanSeat]
	if !human.Eliminated {
		fmt.Fprintln(&b, T("poker.your_hand_label"))
		fmt.Fprintln(&b, renderCardBoxRow(human.HoleCards[:], nil))
	}
	fmt.Fprintln(&b)

	switch {
	case g.Phase == PokerPhaseShowdown:
		fmt.Fprintln(&b, m.renderShowdown())
		fmt.Fprintln(&b)
		fmt.Fprintln(&b, T("poker.hint.next_hand"))
	case m.choosingBetAmount:
		fmt.Fprintln(&b, Tf("poker.bet_amount_line", m.betAmount))
		fmt.Fprintln(&b, T("poker.hint.bet_amount"))
	case g.CurrentPlayerIdx == m.humanSeat:
		fmt.Fprintln(&b, T("poker.hint.your_turn"))
		for i, a := range m.pokerHumanActions() {
			fmt.Fprintf(&b, "[%d] %s\n", i+1, T("poker.action."+a))
		}
	default:
		fmt.Fprintln(&b, T("common.thinking_generic"))
	}

	if m.message != "" {
		if m.isError {
			fmt.Fprintln(&b, errorStyle.Render("! "+m.message))
		} else {
			fmt.Fprintln(&b, infoStyle.Render(m.message))
		}
	}
	fmt.Fprintln(&b)
	fmt.Fprintln(&b, dimStyle.Render(T("poker.hint.quit")))
	return b.String()
}

func (m PokerModel) renderPlayerLine(idx int, p *PokerPlayer) string {
	name := Tf("poker.seat_label", idx+1)
	if idx == m.humanSeat {
		name = T("poker.you_label")
	}
	status := ""
	switch {
	case p.Eliminated:
		status = T("poker.status.eliminated")
	case p.Folded:
		status = T("poker.status.folded")
	case p.AllIn:
		status = T("poker.status.allin")
	}
	marker := ""
	if idx == m.game.DealerIdx {
		marker = " (D)"
	}
	line := Tf("poker.player_line", name+marker, p.Stack, p.CurrentBet, status)
	if idx == m.game.CurrentPlayerIdx && m.game.Phase != PokerPhaseShowdown && m.game.Phase != PokerPhaseTournamentOver {
		return turnStyle.Render(line)
	}
	return line
}

func (m PokerModel) renderShowdown() string {
	var b strings.Builder
	res := m.game.Result
	if res.WentToShowdown {
		fmt.Fprintln(&b, T("poker.showdown.title"))
		for idx, v := range res.HandValues {
			name := Tf("poker.seat_label", idx+1)
			if idx == m.humanSeat {
				name = T("poker.you_label")
			}
			fmt.Fprintf(&b, "  %s: %s %s — %s\n", name,
				pokerCardStr(m.game.Players[idx].HoleCards[0]), pokerCardStr(m.game.Players[idx].HoleCards[1]),
				T(pokerCategoryNameKey[v.Category]))
		}
	} else {
		fmt.Fprintln(&b, T("poker.showdown.fold_title"))
	}
	for idx, amt := range res.Winners {
		if idx == m.humanSeat {
			fmt.Fprintln(&b, Tf("poker.showdown.winner_line_you", amt))
		} else {
			fmt.Fprintln(&b, Tf("poker.showdown.winner_line", Tf("poker.seat_label", idx+1), amt))
		}
	}
	return strings.TrimRight(b.String(), "\n")
}

func (m PokerModel) renderTournamentOver() string {
	for i, p := range m.game.Players {
		if !p.Eliminated {
			name := Tf("poker.seat_label", i+1)
			if i == m.humanSeat {
				return winStyle.Render(T("poker.outcome.win"))
			}
			return errorStyle.Render(Tf("poker.outcome.lose", name))
		}
	}
	return ""
}

// pokerCategoryNameKey — ключи переводов названий покерных
// комбинаций для показа на шоудауне.
var pokerCategoryNameKey = map[PokerHandCategory]string{
	PokerHighCard:      "poker.hand.highcard",
	PokerPair:          "poker.hand.pair",
	PokerTwoPair:       "poker.hand.twopair",
	PokerThreeOfAKind:  "poker.hand.trips",
	PokerStraight:      "poker.hand.straight",
	PokerFlush:         "poker.hand.flush",
	PokerFullHouse:     "poker.hand.fullhouse",
	PokerFourOfAKind:   "poker.hand.quads",
	PokerStraightFlush: "poker.hand.straightflush",
}
