package main

import (
	"fmt"
	"strings"
	"time"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

// botMoveMsg сигнализирует о том, что пора сделать очередной ход
// бота — используется, чтобы ходы ботов были видны человеку с
// небольшой задержкой, а не проваливались мгновенно.
type botMoveMsg struct{}

const botMoveDelay = 500 * time.Millisecond

var (
	titleStyle     = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212"))
	redCardStyle   = lipgloss.NewStyle().Foreground(lipgloss.Color("203"))
	blackCardStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("255"))
	cursorStyle    = lipgloss.NewStyle().Reverse(true).Bold(true)
	dimStyle       = lipgloss.NewStyle().Faint(true)
	infoStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color("87"))
	errorStyle     = lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Bold(true)
	turnStyle      = lipgloss.NewStyle().Foreground(lipgloss.Color("228")).Bold(true)
	winStyle       = lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Bold(true)
)

// Model — состояние TUI поверх GameState.
type Model struct {
	game     *GameState
	humanIdx int
	bots     map[int]BotStrategy

	cursor  int
	staged  map[Card]bool
	message string
	isError bool

	quitting   bool
	backToMenu bool
}

// NewModel создаёт новую партию со стандартными денежными
// параметрами: человек — игрок 0, дальше три бота со случайными
// именами и случайной (не подсказывающей ничего) комбинацией
// уровней сложности.
func NewModel() Model {
	return NewModelWithStakes(DefaultStartingCapital, DefaultAnte, DefaultTonkMultiplier)
}

// NewModelWithStakes создаёт новую партию с явно заданными
// денежными параметрами (стартовый капитал, размер ставки, множитель
// банка при победе через Тонк) — используется экраном настроек
// перед началом игры.
func NewModelWithStakes(startingCapital, ante, tonkMultiplier int) Model {
	botNames, botStrategies := newBotRoster(3)
	names := append([]string{T("common.you")}, botNames...)

	game := NewGameWithStakes(names, 0, startingCapital, ante, tonkMultiplier)
	m := Model{
		game:     game,
		humanIdx: 0,
		bots: map[int]BotStrategy{
			1: botStrategies[0],
			2: botStrategies[1],
			3: botStrategies[2],
		},
		staged: make(map[Card]bool),
	}
	return m
}

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

// maybeScheduleBot возвращает команду, запускающую ход бота, если
// сейчас не очередь человека и раунд не завершён.
func (m Model) maybeScheduleBot() tea.Cmd {
	if m.game.Phase == PhaseGameOver {
		return nil
	}
	if m.game.CurrentPlayer == m.humanIdx {
		return nil
	}
	return tea.Tick(botMoveDelay, func(time.Time) tea.Msg { return botMoveMsg{} })
}

func (m *Model) resetSelectionState() {
	m.staged = make(map[Card]bool)
	if len(m.game.current().Hand) == 0 {
		m.cursor = 0
	} else if m.cursor >= len(m.game.current().Hand) {
		m.cursor = len(m.game.current().Hand) - 1
	}
}

func (m *Model) setInfo(format string, args ...interface{}) {
	m.message = fmt.Sprintf(format, args...)
	m.isError = false
}

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

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

func (m Model) handleBotMove() (tea.Model, tea.Cmd) {
	if m.game.Phase == PhaseGameOver {
		return m, nil
	}
	idx := m.game.CurrentPlayer
	bot, ok := m.bots[idx]
	if !ok {
		// на текущего игрока бот не назначен (это человек) —
		// ничего не делаем, ждём ввода с клавиатуры
		return m, nil
	}
	if err := bot.PlayTurn(m.game); err != nil {
		m.setError(fmt.Errorf(T("бот %s: %w"), m.game.Players[idx].Name, err))
		return m, nil
	}
	m.setInfo(T("tonk.msg.bot_moved"), m.game.Players[idx].Name)
	if m.game.Phase != PhaseGameOver && m.game.CurrentPlayer == m.humanIdx {
		m.resetSelectionState()
	}
	return m, m.maybeScheduleBot()
}

func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	key := msg.String()

	if key == "ctrl+c" {
		m.quitting = true
		return m, tea.Quit
	}

	if m.game.Phase == PhaseGameOver {
		switch key {
		case "n":
			m.startNewRound()
			return m, m.maybeScheduleBot()
		case "q":
			m.backToMenu = true
			return m, nil
		}
		return m, nil
	}

	if key == "q" {
		m.backToMenu = true
		return m, nil
	}

	if m.game.CurrentPlayer != m.humanIdx {
		// сейчас ходит бот — игнорируем ввод
		return m, nil
	}

	switch m.game.Phase {
	case PhaseDraw:
		return m.handleDrawKey(key)
	case PhaseAction:
		return m.handleActionKey(key)
	}
	return m, nil
}

func (m Model) handleDrawKey(key string) (tea.Model, tea.Cmd) {
	switch key {
	case "d":
		if _, err := m.game.DrawFromDeck(); err != nil {
			m.setError(err)
			return m, nil
		}
		m.setInfo(T("tonk.msg.drew_deck"))
		m.resetSelectionState()
	case "p":
		card, err := m.game.DrawFromDiscard()
		if err != nil {
			m.setError(err)
			return m, nil
		}
		m.setInfo(T("tonk.msg.drew_discard"), card)
		m.resetSelectionState()
	case "k":
		res, err := m.game.Drop()
		if err != nil {
			m.setError(err)
			return m, nil
		}
		if res.DropCaught {
			m.setInfo(T("tonk.msg.drop_caught"), m.game.Players[res.Winner].Name)
		} else {
			m.setInfo(T("tonk.msg.drop_success"))
		}
	}
	return m, nil
}

func (m Model) handleActionKey(key string) (tea.Model, tea.Cmd) {
	hand := m.game.current().Hand

	switch key {
	case "left", "h":
		if m.cursor > 0 {
			m.cursor--
		}
	case "right", "l":
		if m.cursor < len(hand)-1 {
			m.cursor++
		}
	case " ":
		if len(hand) == 0 {
			return m, nil
		}
		c := hand[m.cursor]
		if m.staged[c] {
			delete(m.staged, c)
		} else {
			m.staged[c] = true
		}
	case "m":
		return m.handleLayMeld()
	case "t":
		res, err := m.game.DeclareTonk()
		if err != nil {
			m.setError(err)
			return m, nil
		}
		m.setInfo(T("tonk.msg.tonk_declared"))
		_ = res
		return m, m.maybeScheduleBot()
	case "enter":
		if len(hand) == 0 {
			return m, nil
		}
		card := hand[m.cursor]
		if err := m.game.Discard(card); err != nil {
			m.setError(err)
			return m, nil
		}
		m.setInfo(T("tonk.msg.discarded"), card)
		if m.game.Phase != PhaseGameOver {
			m.resetSelectionState()
		}
		return m, m.maybeScheduleBot()
	case "1", "2", "3", "4", "5", "6", "7", "8", "9":
		idx := int(key[0] - '1')
		return m.handleAddToMeld(idx)
	}
	return m, nil
}

func (m Model) handleLayMeld() (tea.Model, tea.Cmd) {
	if len(m.staged) < 3 {
		m.setError(fmt.Errorf(T("выберите (пробел) минимум 3 карты для комбинации")))
		return m, nil
	}
	cards := make([]Card, 0, len(m.staged))
	for c := range m.staged {
		cards = append(cards, c)
	}
	if err := m.game.LayNewMeld(cards); err != nil {
		m.setError(err)
		return m, nil
	}
	m.setInfo(T("tonk.msg.meld_laid"))
	if m.game.Phase != PhaseGameOver {
		m.resetSelectionState()
		return m, nil
	}
	return m, m.maybeScheduleBot()
}

func (m Model) handleAddToMeld(meldIdx int) (tea.Model, tea.Cmd) {
	hand := m.game.current().Hand
	if len(hand) == 0 {
		return m, nil
	}
	if meldIdx < 0 || meldIdx >= len(m.game.Table) {
		m.setError(fmt.Errorf(T("на столе нет комбинации №%d"), meldIdx+1))
		return m, nil
	}
	card := hand[m.cursor]
	if err := m.game.AddToMeld(meldIdx, card); err != nil {
		m.setError(err)
		return m, nil
	}
	m.setInfo(T("tonk.msg.card_added"), card, meldIdx+1)
	if m.game.Phase != PhaseGameOver {
		m.resetSelectionState()
		return m, nil
	}
	return m, m.maybeScheduleBot()
}

// startNewRound начинает новый раунд, перенося капитал игроков из
// завершившегося раунда и заново собирая анте в банк. Перед этим
// проверяет, не остался ли кто-то без денег (см. checkBankruptcies)
// — такие игроки заменяются новыми ботами случайного уровня со
// стартовым капиталом.
func (m *Model) startNewRound() {
	bankruptMsg := m.checkBankruptcies()
	m.game = m.game.NextRound()
	m.staged = make(map[Card]bool)
	m.cursor = 0
	if bankruptMsg != "" {
		m.setInfo("%s", bankruptMsg)
	} else {
		m.message = ""
		m.isError = false
	}
}

// checkBankruptcies проверяет капитал каждого игрока после
// завершившегося раунда: у кого баланс опустился до нуля или ниже,
// тот выбывает и заменяется новым ботом — со случайным именем
// (по возможности не повторяющим тех, кто уже сидит за столом) и
// случайным уровнем сложности, который нигде не показывается. Если
// выбывает сам человек, его место тоже занимает бот, и humanIdx
// становится -1 (партия продолжается уже полностью автоматически,
// человек может понаблюдать или выйти в меню). Возвращает текстовое
// сообщение об произошедших заменах (пустая строка, если
// банкротств не было).
func (m *Model) checkBankruptcies() string {
	var messages []string
	for i, p := range m.game.Players {
		if p.Balance > 0 {
			continue
		}

		used := make(map[string]bool, len(m.game.Players))
		for _, other := range m.game.Players {
			used[other.Name] = true
		}
		newName := pickBotName(used)
		bot, _ := randomBotStrategy()
		wasHuman := p.IsHuman
		oldName := p.Name

		p.Name = newName
		p.Balance = m.game.StartingCapital
		p.IsHuman = false
		m.bots[i] = bot

		if wasHuman {
			m.humanIdx = -1
			messages = append(messages, fmt.Sprintf(T("tonk.bankrupt.human"), newName))
		} else {
			messages = append(messages, fmt.Sprintf(T("tonk.bankrupt.bot"), oldName, newName, p.Balance))
		}
	}
	return strings.Join(messages, " ")
}

func isRed(s Suit) bool {
	return s == Hearts || s == Diamonds
}

// cardColorStyle возвращает базовый (только цвет) стиль карты, не
// применяя его — используется, когда нужно доклеить к цвету ещё
// какие-то атрибуты (подчёркивание и т.п.) одним стилем, вместо
// вложенных вызовов Render (что ломается в некоторых терминалах).
func cardColorStyle(c Card) lipgloss.Style {
	if isRed(c.Suit) {
		return redCardStyle
	}
	return blackCardStyle
}

func renderCard(c Card) string {
	return cardColorStyle(c).Render(c.String())
}

func (m Model) renderHand() string {
	hand := m.game.current().Hand
	if len(hand) == 0 {
		return dimStyle.Render(T("common.no_cards"))
	}
	parts := make([]string, len(hand))
	for i, c := range hand {
		var text string
		switch {
		case i == m.cursor:
			// курсор — поверх обычного (нецветного) текста карты,
			// как и раньше
			text = cursorStyle.Render(c.String())
		case m.staged[c]:
			// важно: цвет и подчёркивание в ОДНОМ стиле, а не
			// Render() поверх уже отрендеренной (цветной) строки —
			// вложенные ANSI-последовательности ломаются в
			// некоторых терминалах (вложенный сброс обрывает
			// внешний стиль раньше времени)
			text = cardColorStyle(c).Underline(true).Bold(true).Render(c.String())
		default:
			text = renderCard(c)
		}
		parts[i] = text
	}
	return strings.Join(parts, " ")
}

func (m Model) renderTable() string {
	if len(m.game.Table) == 0 {
		return dimStyle.Render(T("tonk.table_empty"))
	}
	var b strings.Builder
	for i, meld := range m.game.Table {
		kind := T("tonk.set")
		if meld.Kind == KindSequence {
			kind = T("tonk.sequence")
		}
		cardsStr := make([]string, len(meld.Cards))
		for j, c := range meld.Cards {
			cardsStr[j] = renderCard(c)
		}
		fmt.Fprintf(&b, T("tonk.meld_line"), i+1, kind,
			m.game.Players[meld.Owner].Name, strings.Join(cardsStr, " "))
	}
	return strings.TrimRight(b.String(), "\n")
}

func (m Model) renderPlayers() string {
	var b strings.Builder
	for i, p := range m.game.Players {
		marker := "  "
		if i == m.game.CurrentPlayer {
			marker = "» "
		}
		name := p.Name
		line := fmt.Sprintf(T("tonk.player_line"), marker, name, len(p.Hand), p.Balance)
		if i == m.game.CurrentPlayer {
			line = turnStyle.Render(line)
		}
		fmt.Fprintln(&b, line)
	}
	return strings.TrimRight(b.String(), "\n")
}

func (m Model) View() string {
	if m.quitting {
		return T("common.goodbye")
	}

	var b strings.Builder
	fmt.Fprintln(&b, titleStyle.Render(T("tonk.title")))
	fmt.Fprintln(&b)
	fmt.Fprintln(&b, m.renderPlayers())
	fmt.Fprintln(&b)

	top, ok := m.game.PeekDiscard()
	topStr := "—"
	if ok {
		topStr = renderCard(top)
	}
	fmt.Fprintf(&b, T("tonk.status_line"),
		m.game.Deck.Len(), topStr, m.game.Pot, m.game.AnteAmount, m.game.TonkMultiplier)

	fmt.Fprintln(&b, T("tonk.melds_label"))
	fmt.Fprintln(&b, m.renderTable())
	fmt.Fprintln(&b)

	if m.game.Phase == PhaseGameOver {
		fmt.Fprintln(&b, m.renderRoundResult())
		fmt.Fprintln(&b)
		fmt.Fprintln(&b, dimStyle.Render(T("tonk.hint.gameover")))
		return b.String()
	}

	if m.game.CurrentPlayer == m.humanIdx {
		fmt.Fprintln(&b, T("common.your_hand"))
		fmt.Fprintln(&b, m.renderHand())
		fmt.Fprintf(&b, T("tonk.hand_weight"), HandValue(m.game.current().Hand))

		switch m.game.Phase {
		case PhaseDraw:
			fmt.Fprintln(&b, Tf("tonk.hint.draw", topStr))
		case PhaseAction:
			tonkHint := ""
			if CanDeclareTonk(m.game.current().Hand, m.game.TonkThreshold) {
				tonkHint = winStyle.Render(T("tonk.hint.tonk_available"))
			}
			fmt.Fprintln(&b, T("tonk.hint.action1"))
			fmt.Fprintln(&b, T("tonk.hint.action2")+tonkHint)
		}
	} else {
		fmt.Fprintf(&b, T("common.thinking"), m.game.Players[m.game.CurrentPlayer].Name)
	}

	fmt.Fprintln(&b)
	if m.message != "" {
		if m.isError {
			fmt.Fprintln(&b, errorStyle.Render("! "+m.message))
		} else {
			fmt.Fprintln(&b, infoStyle.Render(m.message))
		}
	}

	return b.String()
}

func (m Model) renderRoundResult() string {
	res := m.game.Result
	var b strings.Builder
	switch {
	case res.DeadRound:
		fmt.Fprintln(&b, errorStyle.Render(Tf("tonk.result.dead", res.Pot)))
	case res.TonkCall:
		fmt.Fprintln(&b, winStyle.Render(Tf("tonk.result.tonk",
			m.game.Players[res.Winner].Name, m.game.TonkMultiplier, res.Pot*m.game.TonkMultiplier)))
	case res.DropCall && res.DropCaught:
		fmt.Fprintln(&b, errorStyle.Render(Tf("tonk.result.drop_caught", m.game.Players[res.Winner].Name)))
	case res.DropCall:
		fmt.Fprintln(&b, winStyle.Render(Tf("tonk.result.drop_success", m.game.Players[res.Winner].Name, res.Pot)))
	default:
		fmt.Fprintln(&b, winStyle.Render(Tf("tonk.result.win", m.game.Players[res.Winner].Name, res.Pot)))
	}
	for i, p := range m.game.Players {
		fmt.Fprintf(&b, T("tonk.result.delta_line"), p.Name, res.BalanceDeltas[i], p.Balance)
	}
	return strings.TrimRight(b.String(), "\n")
}
