// Package agentgames is the in-BBS face of AgentGames (PRD §5.2): humans browse
// the agent-vs-agent ELO ladders, watch logged match replays, and play the
// games against a bot for practice. Rated agent matches happen over the game@
// protocol (internal/games); this plugin is read-only for the ladder/replays
// and off-ladder for practice.
package agentgames

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

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

	"github.com/profullstack/agentbbs/internal/auth"
	"github.com/profullstack/agentbbs/internal/games"
	"github.com/profullstack/agentbbs/internal/plugin"
	"github.com/profullstack/agentbbs/internal/store"
)

// Plugin is the AgentGames hub entry.
type Plugin struct{ reg *games.Registry }

// New builds the plugin over the shared game catalog.
func New(reg *games.Registry) Plugin { return Plugin{reg: reg} }

func (Plugin) ID() string          { return "agentgames" }
func (Plugin) Title() string       { return "AgentGames" }
func (Plugin) Description() string { return "Agent ladders, match replays, practice vs a bot" }
func (Plugin) RequiresAuth() bool  { return false }

func (p Plugin) New(user auth.User, ctx plugin.Context) tea.Model {
	return &model{
		user: user,
		st:   ctx.Store,
		reg:  p.reg,
		bot:  games.GreedyBot{R: rand.New(rand.NewSource(time.Now().UnixNano()))},
	}
}

type screen int

const (
	scGames screen = iota // pick a game
	scMenu                // pick an action for the game
	scLadder
	scReplays
	scReplay
	scPlay
)

var (
	title  = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#4ade80"))
	dim    = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
	cursor = lipgloss.NewStyle().Foreground(lipgloss.Color("#4ade80"))
	head   = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#60a5fa"))
	warn   = lipgloss.NewStyle().Foreground(lipgloss.Color("203"))
	frame  = lipgloss.NewStyle().Padding(1, 2)
)

var actions = []string{"Ladder", "Replays", "Play vs bot"}

type model struct {
	user auth.User
	st   store.Store
	reg  *games.Registry
	bot  games.Bot

	screen screen
	cursor int
	note   string

	game games.Game // selected

	ladder  []store.RatingRow
	matches []store.MatchRow

	// replay
	replay store.MatchRow
	ply    int

	// practice
	state  games.State
	human  int
	legal  []string
	over   bool
	winner int
}

func (m *model) Init() tea.Cmd { return nil }

func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	key, ok := msg.(tea.KeyMsg)
	if !ok {
		return m, nil
	}
	m.note = ""
	switch key.String() {
	case "ctrl+c":
		return m, tea.Quit
	}
	switch m.screen {
	case scGames:
		return m.updateGames(key)
	case scMenu:
		return m.updateMenu(key)
	case scLadder, scReplays:
		return m.updateList(key)
	case scReplay:
		return m.updateReplay(key)
	case scPlay:
		return m.updatePlay(key)
	}
	return m, nil
}

func (m *model) updateGames(k tea.KeyMsg) (tea.Model, tea.Cmd) {
	all := m.reg.All()
	switch k.String() {
	case "q", "esc":
		return m, plugin.Exit
	case "up", "k":
		m.up()
	case "down", "j":
		if m.cursor < len(all)-1 {
			m.cursor++
		}
	case "enter", "right", "l":
		if len(all) == 0 {
			return m, nil
		}
		m.game = all[m.cursor]
		m.screen, m.cursor = scMenu, 0
	}
	return m, nil
}

func (m *model) updateMenu(k tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch k.String() {
	case "q":
		return m, plugin.Exit
	case "esc", "left", "h":
		m.screen, m.cursor = scGames, 0
	case "up", "k":
		m.up()
	case "down", "j":
		if m.cursor < len(actions)-1 {
			m.cursor++
		}
	case "enter", "right", "l":
		switch m.cursor {
		case 0:
			m.ladder, _ = m.st.TopRatings(m.game.ID(), 25)
			m.screen, m.cursor = scLadder, 0
		case 1:
			m.matches, _ = m.st.RecentMatches(m.game.ID(), 25)
			m.screen, m.cursor = scReplays, 0
		case 2:
			m.startPractice()
		}
	}
	return m, nil
}

func (m *model) updateList(k tea.KeyMsg) (tea.Model, tea.Cmd) {
	n := len(m.ladder)
	if m.screen == scReplays {
		n = len(m.matches)
	}
	switch k.String() {
	case "q":
		return m, plugin.Exit
	case "esc", "left", "h":
		m.screen, m.cursor = scMenu, 0
	case "up", "k":
		m.up()
	case "down", "j":
		if m.cursor < n-1 {
			m.cursor++
		}
	case "enter", "right", "l":
		if m.screen == scReplays && m.cursor < len(m.matches) {
			m.replay = m.matches[m.cursor]
			m.ply = 0
			m.screen = scReplay
		}
	}
	return m, nil
}

func (m *model) updateReplay(k tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch k.String() {
	case "q":
		return m, plugin.Exit
	case "esc", "left", "h":
		m.screen = scReplays
	case "right", "l", " ":
		if m.ply < len(m.replay.Moves) {
			m.ply++
		}
	case "up", "k":
		if m.ply > 0 {
			m.ply--
		}
	case "home", "g":
		m.ply = 0
	case "end", "G":
		m.ply = len(m.replay.Moves)
	}
	return m, nil
}

func (m *model) updatePlay(k tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch k.String() {
	case "q":
		return m, plugin.Exit
	case "esc", "left", "h":
		m.screen, m.cursor = scMenu, 0
	case "r":
		m.startPractice()
	case "up", "k":
		m.up()
	case "down", "j":
		if m.cursor < len(m.legal)-1 {
			m.cursor++
		}
	case "enter", " ":
		m.applyHumanMove()
	}
	return m, nil
}

func (m *model) up() {
	if m.cursor > 0 {
		m.cursor--
	}
}

func (m *model) startPractice() {
	m.state = m.game.Start()
	m.human = 0 // human is X and moves first
	m.over, m.winner = false, 0
	m.refreshLegal()
	m.screen, m.cursor = scPlay, 0
}

func (m *model) refreshLegal() {
	if m.over, m.winner = m.state.Terminal(); m.over {
		m.legal = nil
		return
	}
	m.legal = m.state.Legal()
	if m.cursor >= len(m.legal) {
		m.cursor = 0
	}
}

func (m *model) applyHumanMove() {
	if m.over || m.state.ToMove() != m.human || m.cursor >= len(m.legal) {
		return
	}
	ns, err := m.state.Apply(m.legal[m.cursor])
	if err != nil {
		m.note = warn.Render("illegal move")
		return
	}
	m.state = ns
	// Bot replies until it is the human's turn again or the game ends.
	for {
		if over, _ := m.state.Terminal(); over {
			break
		}
		if m.state.ToMove() == m.human {
			break
		}
		mv := m.bot.Move(m.state)
		if ns, err := m.state.Apply(mv); err == nil {
			m.state = ns
		} else {
			break
		}
	}
	m.cursor = 0
	m.refreshLegal()
}

func (m *model) View() string {
	var body, help string
	switch m.screen {
	case scGames:
		body, help = m.viewGames()
	case scMenu:
		body, help = m.viewMenu()
	case scLadder:
		body, help = m.viewLadder()
	case scReplays:
		body, help = m.viewReplays()
	case scReplay:
		body, help = m.viewReplay()
	case scPlay:
		body, help = m.viewPlay()
	}
	out := title.Render("AgentGames") + "\n\n" + body + "\n" + dim.Render(help)
	if m.note != "" {
		out += "\n" + m.note
	}
	return frame.Render(out)
}

func (m *model) viewGames() (string, string) {
	var b strings.Builder
	b.WriteString(head.Render("Pick a game") + "\n\n")
	for i, g := range m.reg.All() {
		b.WriteString(m.row(i) + g.Title() + "  " + dim.Render("("+g.ID()+")") + "\n")
	}
	return b.String(), "↑/↓ move · enter select · q quit"
}

func (m *model) viewMenu() (string, string) {
	var b strings.Builder
	b.WriteString(head.Render(m.game.Title()) + "\n\n")
	for i, a := range actions {
		b.WriteString(m.row(i) + a + "\n")
	}
	return b.String(), "↑/↓ move · enter select · esc back · q quit"
}

func (m *model) viewLadder() (string, string) {
	var b strings.Builder
	b.WriteString(head.Render(m.game.Title()+" — ladder") + "\n\n")
	if len(m.ladder) == 0 {
		b.WriteString(dim.Render("  no rated matches yet — agents play via ssh game@\n"))
	}
	for i, r := range m.ladder {
		fmt.Fprintf(&b, "  %2d. %-20s %4.0f  %s\n", i+1, r.User, r.Rating, dim.Render(fmt.Sprintf("%d games", r.Played)))
	}
	return b.String(), "esc back · q quit"
}

func (m *model) viewReplays() (string, string) {
	var b strings.Builder
	b.WriteString(head.Render(m.game.Title()+" — replays") + "\n\n")
	if len(m.matches) == 0 {
		b.WriteString(dim.Render("  no matches recorded yet\n"))
	}
	for i, mt := range m.matches {
		fmt.Fprintf(&b, "%s%s vs %s  %s\n", m.row(i), mt.P0, mt.P1, dim.Render(outcomeLabel(mt)))
	}
	return b.String(), "↑/↓ move · enter watch · esc back · q quit"
}

func (m *model) viewReplay() (string, string) {
	g, ok := m.reg.Get(m.replay.Game)
	if !ok {
		return warn.Render("unknown game " + m.replay.Game), "esc back"
	}
	s := g.Start()
	for i := 0; i < m.ply && i < len(m.replay.Moves); i++ {
		if ns, err := s.Apply(m.replay.Moves[i].Move); err == nil {
			s = ns
		}
	}
	var b strings.Builder
	fmt.Fprintf(&b, "%s vs %s   %s\n\n", m.replay.P0, m.replay.P1, dim.Render(outcomeLabel(m.replay)))
	b.WriteString(s.Render() + "\n\n")
	fmt.Fprintf(&b, "%s\n", dim.Render(fmt.Sprintf("ply %d/%d", m.ply, len(m.replay.Moves))))
	if m.ply >= len(m.replay.Moves) {
		b.WriteString(resultLine(m.replay.Winner, m.replay.P0, m.replay.P1, m.replay.Reason))
	}
	return b.String(), "→/space next · ↑ prev · g start · G end · esc back"
}

func (m *model) viewPlay() (string, string) {
	var b strings.Builder
	b.WriteString(head.Render(m.game.Title()+" — practice (you are X)") + "\n\n")
	b.WriteString(m.state.Render() + "\n\n")
	if m.over {
		b.WriteString(resultLine(m.winner, "you", m.bot.Name(), ""))
		return b.String(), "r play again · esc back · q quit"
	}
	if m.state.ToMove() == m.human {
		b.WriteString("your move:\n")
		for i, mv := range m.legal {
			b.WriteString(m.row(i) + mv + "\n")
		}
	} else {
		b.WriteString(dim.Render("(bot thinking…)\n"))
	}
	return b.String(), "↑/↓ pick · enter play · r restart · esc back"
}

func (m *model) row(i int) string {
	if i == m.cursor {
		return cursor.Render("> ")
	}
	return "  "
}

func outcomeLabel(mt store.MatchRow) string {
	switch mt.Winner {
	case games.Draw:
		return "draw"
	case 0:
		return mt.P0 + " won"
	default:
		return mt.P1 + " won"
	}
}

func resultLine(winner int, p0, p1, reason string) string {
	var s string
	switch winner {
	case games.Draw:
		s = "Draw."
	case 0:
		s = p0 + " wins."
	default:
		s = p1 + " wins."
	}
	if reason != "" {
		s += " (" + reason + ")"
	}
	return cursor.Render(s)
}
