package ui

import (
	_ "embed"
	"fmt"
	"strings"
	"time"

	"termdoku/internal/achievements"
	"termdoku/internal/config"
	"termdoku/internal/generator"
	"termdoku/internal/stats"
	"termdoku/internal/theme"

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

//go:embed assets/banner.txt
var bannerArt string

type appState int

const (
	stateMenu appState = iota
	stateGame
	stateStats
	stateAchievements
	stateLeaderboard
	stateProfile
	stateProfileSubmenu
	stateDatabase
)

type App struct {
	state        appState
	cfg          config.Config
	th           theme.Theme
	styles       UIStyles
	stats        stats.Stats
	achievements *achievements.Manager

	menuItems    []string
	selectedIdx  int
	autoCheck    bool
	timerEnabled bool

	width  int
	height int

	currentDiff string
	game        Model

	// Profile submenu
	profileMenuItems   []string
	profileSelectedIdx int
}

func NewApp(cfg config.Config) App {
	th := theme.GetTheme(cfg.Theme)
	st, _ := stats.Load()
	ach, _ := achievements.Load()
	return App{
		state:              stateMenu,
		cfg:                cfg,
		th:                 th,
		styles:             BuildStyles(th),
		stats:              st,
		achievements:       ach,
		menuItems:          []string{"Easy", "Normal", "Hard", "Expert", "Lunatic", "Daily", "Profile"},
		selectedIdx:        1,
		autoCheck:          cfg.AutoCheck,
		timerEnabled:       cfg.TimerEnabled,
		profileMenuItems:   []string{"Achievements", "Leaderboard"},
		profileSelectedIdx: 0,
	}
}

func (a App) Init() tea.Cmd { return nil }

func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch a.state {
	case stateMenu:
		switch m := msg.(type) {
		case tea.KeyMsg:
			s := m.String()
			switch s {
			case "up", "k":
				a.selectedIdx = clamp(a.selectedIdx-1, 0, len(a.menuItems)-1)
			case "down", "j":
				a.selectedIdx = clamp(a.selectedIdx+1, 0, len(a.menuItems)-1)
			case "left", "h":
				a.selectedIdx = clamp(a.selectedIdx-1, 0, len(a.menuItems)-1)
			case "right", "l":
				a.selectedIdx = clamp(a.selectedIdx+1, 0, len(a.menuItems)-1)
			case "a":
				a.autoCheck = !a.autoCheck
			case "t":
				a.timerEnabled = !a.timerEnabled
			case "enter":
				sel := a.menuItems[a.selectedIdx]
				switch sel {
				case "Achievements":
					a.state = stateAchievements
					return a, nil
				case "Leaderboard":
					a.state = stateLeaderboard
					return a, nil
				case "Profile":
					a.state = stateProfile
					return a, nil
				default:
					gm, cmd := a.startGame()
					a.game = gm
					a.state = stateGame
					return a, cmd
				}
			case "q", "esc", "ctrl+c":
				return a, tea.Quit
			}
		case tea.WindowSizeMsg:
			a.width, a.height = m.Width, m.Height
		}
		return a, nil
	case stateStats:
		switch m := msg.(type) {
		case tea.KeyMsg:
			s := m.String()
			switch s {
			case "m", "q", "esc", "enter":
				a.state = stateMenu
				return a, nil
			}
		case tea.WindowSizeMsg:
			a.width, a.height = m.Width, m.Height
		}
		return a, nil
	case stateGame:
		// Check if game was just completed
		wasCompleted := a.game.completed

		// intercept main menu key
		if kmsg, isKey := msg.(tea.KeyMsg); isKey {
			if kmsg.String() == "m" {
				// Record game if it was completed but not yet recorded
				if a.game.completed && !wasCompleted {
					a.recordGameCompletion()
				}
				a.state = stateMenu
				return a, nil
			}
		}

		gm, cmd := a.game.Update(msg)
		if v, ok := gm.(Model); ok {
			// Check if game just became completed
			if v.completed && !wasCompleted {
				a.game = v
				a.recordGameCompletion()
				return a, cmd
			}
			a.game = v
		}
		return a, cmd
	case stateAchievements:
		switch m := msg.(type) {
		case tea.KeyMsg:
			s := m.String()
			switch s {
			case "m", "q", "esc", "enter":
				a.state = stateMenu
				return a, nil
			}
		case tea.WindowSizeMsg:
			a.width, a.height = m.Width, m.Height
		}
		return a, nil
	case stateLeaderboard:
		switch m := msg.(type) {
		case tea.KeyMsg:
			s := m.String()
			switch s {
			case "m", "q", "esc", "enter":
				a.state = stateMenu
				return a, nil
			}
		case tea.WindowSizeMsg:
			a.width, a.height = m.Width, m.Height
		}
		return a, nil
	case stateProfile:
		switch m := msg.(type) {
		case tea.KeyMsg:
			s := m.String()
			switch s {
			case "m", "q", "esc", "enter":
				a.state = stateMenu
				return a, nil
			case "s":
				a.state = stateProfileSubmenu
				return a, nil
			case "d":
				a.state = stateDatabase
				return a, nil
			}
		case tea.WindowSizeMsg:
			a.width, a.height = m.Width, m.Height
		}
		return a, nil
	case stateProfileSubmenu:
		switch m := msg.(type) {
		case tea.KeyMsg:
			s := m.String()
			switch s {
			case "m", "q", "esc":
				a.state = stateProfile
				return a, nil
			case "up", "k":
				a.profileSelectedIdx = clamp(a.profileSelectedIdx-1, 0, len(a.profileMenuItems)-1)
			case "down", "j":
				a.profileSelectedIdx = clamp(a.profileSelectedIdx+1, 0, len(a.profileMenuItems)-1)
			case "enter":
				sel := a.profileMenuItems[a.profileSelectedIdx]
				switch sel {
				case "Stats":
					a.state = stateStats
					return a, nil
				case "Achievements":
					a.state = stateAchievements
					return a, nil
				case "Leaderboard":
					a.state = stateLeaderboard
					return a, nil
				}
			}
		case tea.WindowSizeMsg:
			a.width, a.height = m.Width, m.Height
		}
		return a, nil
	case stateDatabase:
		switch m := msg.(type) {
		case tea.KeyMsg:
			s := m.String()
			switch s {
			case "m", "q", "esc", "enter":
				a.state = stateMenu
				return a, nil
			case "p":
				a.state = stateProfile
				return a, nil
			}
		case tea.WindowSizeMsg:
			a.width, a.height = m.Width, m.Height
		}
		return a, nil
	}
	return a, nil
}

func (a App) View() string {
	switch a.state {
	case stateMenu:
		return a.viewMenu()
	case stateGame:
		return a.viewGame()
	case stateAchievements:
		return a.viewAchievements()
	case stateProfile:
		return a.viewProfile()
	case stateProfileSubmenu:
		return a.viewProfileSubmenu()
	case stateDatabase:
		return a.viewDatabaseInfo()
	case stateLeaderboard:
		return a.viewLeaderboard()
	}
	return ""
}

func (a App) recordGameCompletion() {
	record := stats.GameRecord{
		Difficulty: a.currentDiff,
		Completed:  a.game.completed,
		Time:       int(a.game.elapsed.Seconds()),
		HintsUsed:  a.game.hintsUsed,
		Date:       time.Now(),
		IsDaily:    a.currentDiff == "Daily",
	}
	if a.currentDiff == "Daily" {
		record.DailySeed = time.Now().Format("2006-01-02")
	}
	a.stats.RecordGame(record)
	_ = stats.Save(a.stats)

	if a.game.completed {
		a.achievements.CheckAndUnlock("first_win", a.stats.CompletedGames)

		if a.game.hintsUsed == 0 {
			a.achievements.CheckAndUnlock("perfectionist", 1)
		}

		if a.currentDiff == "Easy" && int(a.game.elapsed.Seconds()) < 180 {
			a.achievements.CheckAndUnlock("speed_demon", 1)
		}

		if a.currentDiff == "Hard" && !a.autoCheck {
			a.achievements.CheckAndUnlock("no_mistakes", 1)
		}

		a.achievements.CheckAndUnlock("streak_master", a.stats.CurrentStreak)
		a.achievements.CheckAndUnlock("century", a.stats.CompletedGames)

		if a.currentDiff == "Lunatic" {
			lunaticCount := a.stats.CompletionCounts["Lunatic"]
			a.achievements.CheckAndUnlock("lunatic_legend", lunaticCount)
		}

		if a.currentDiff == "Daily" {
			dailyCount := len(a.stats.DailyHistory)
			a.achievements.CheckAndUnlock("daily_devotee", dailyCount)
		}

		_ = achievements.Save(a.achievements)
	}
}

func (a *App) startGame() (Model, tea.Cmd) {
	var g generator.Grid
	var err error
	sel := a.menuItems[a.selectedIdx]
	switch sel {
	case "Daily":
		g, err = generator.GenerateDaily(time.Now())
	case "Easy":
		g, err = generator.Generate(generator.Easy, "")
	case "Normal":
		g, err = generator.Generate(generator.Normal, "")
	case "Hard":
		g, err = generator.Generate(generator.Hard, "")
	case "Expert":
		g, err = generator.Generate(generator.Expert, "")
	case "Lunatic":
		g, err = generator.Generate(generator.Lunatic, "")
	}
	if err != nil {
		return a.game, nil
	}
	cfg := a.cfg
	cfg.AutoCheck = a.autoCheck
	cfg.TimerEnabled = a.timerEnabled
	a.currentDiff = sel
	m := New(g, a.th, cfg)
	m.difficulty = sel
	adaptiveColors := theme.NewAdaptiveColors(a.th)
	diffColors := adaptiveColors.GetDifficultyColors()
	hex := diffColors[sel]
	if hex == "" {
		hex = a.th.Palette.Accent
	}
	style := lipgloss.NewStyle().Foreground(lipgloss.Color(hex))
	m.styles.RowSep = style
	m.styles.ColSep = style
	m.styles.CellFixed = m.styles.CellFixed.Foreground(lipgloss.Color(hex))
	return m, m.Init()
}

func (a App) viewMenu() string {
	banner := bannerArt

	// Options
	optAC := fmt.Sprintf("Auto-Check (a): %s", boolText(a.styles, a.autoCheck))
	optTM := fmt.Sprintf("Timer (t): %s", boolText(a.styles, a.timerEnabled))

	// Adaptive colors
	adaptiveColors := theme.NewAdaptiveColors(a.th)
	accentColors := adaptiveColors.GetAccentColors()

	// Display all menu items
	var items []string
	selectedStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(accentColors["selected"])).Bold(true)
	for i, name := range a.menuItems {
		prefix := "  "
		if i == a.selectedIdx {
			prefix := "✭ "
			label := prefix + name
			items = append(items, selectedStyle.Render(label))
		} else {
			label := prefix + name
			items = append(items, a.styles.MenuItem.Render(label))
		}
	}
	gap := strings.Repeat(" ", 2)
	diffRow := strings.Join(items, gap)

	// Adaptive gradient colors
	gradientColors := adaptiveColors.GetGradientColors()
	bannerGrad := gradientColors["banner"]
	leftHex := bannerGrad[0]
	rightHex := bannerGrad[1]

	title := gradientText("Select option", leftHex, rightHex)
	box := renderGradientBox(diffRow, 2, leftHex, rightHex)
	// Gradient banner (line by line)
	var gb strings.Builder
	for i, l := range strings.Split(strings.TrimRight(banner, "\n"), "\n") {
		gb.WriteString(gradientText(l, leftHex, rightHex))
		if i < len(strings.Split(strings.TrimRight(banner, "\n"), "\n"))-1 {
			gb.WriteString("\n")
		}
	}
	gradientBanner := gb.String()

	// Compose content with explicit 2-line top/bottom padding
	content := "\n\n" + gradientBanner + "\n\n\n" + optAC + "\n" + optTM + "\n\n\n" + title + "\n" + box + "\n\n"
	panel := a.styles.Panel.Render(content)
	if a.width > 0 && a.height > 0 {
		return a.styles.App.Render(lipgloss.Place(a.width, a.height, lipgloss.Center, lipgloss.Center, panel))
	}
	return a.styles.App.Render(panel)
}

func boolText(s UIStyles, v bool) string {
	if v {
		return s.BoolTrue.Render("ON")
	}
	return s.BoolFalse.Render("OFF")
}

func (a App) viewGame() string {
	innerWidth := 58

	boardAndStatus := Render(a.game)

	label := a.currentDiff
	if a.currentDiff == "Daily" {
		label = "Daily Seed"
	}
	headerText := label + " Mode"
	// Adaptive colors for headers
	adaptiveColors := theme.NewAdaptiveColors(a.th)
	gradientColors := adaptiveColors.GetGradientColors()

	var header string
	switch a.currentDiff {
	case "Easy":
		easyGrad := gradientColors["easy"]
		header = gradientText(headerText, easyGrad[0], easyGrad[1])
	case "Normal":
		normalGrad := gradientColors["normal"]
		header = gradientText(headerText, normalGrad[0], normalGrad[1])
	case "Hard":
		hardGrad := gradientColors["hard"]
		header = gradientText(headerText, hardGrad[0], hardGrad[1])
	case "Lunatic":
		lunaticGrad := gradientColors["lunatic"]
		header = gradientText(headerText, lunaticGrad[0], lunaticGrad[1])
	case "Daily":
		dailyGrad := gradientColors["daily"]
		header = gradientText(headerText, dailyGrad[0], dailyGrad[1])
	default:
		header = lipgloss.NewStyle().Foreground(lipgloss.Color(a.th.Palette.Accent)).Bold(true).Render(headerText)
	}

	headerCentered := lipgloss.PlaceHorizontal(innerWidth, lipgloss.Center, header)
	centered := lipgloss.PlaceHorizontal(innerWidth, lipgloss.Center, boardAndStatus)
	body := "\n" + headerCentered + "\n\n" + centered + "\n"
	panel := a.styles.Panel.Render(body)
	if a.width > 0 && a.height > 0 {
		return a.styles.App.Render(lipgloss.Place(a.width, a.height, lipgloss.Center, lipgloss.Center, panel))
	}
	return a.styles.App.Render(panel)
}

// Helpers: gradient text and gradient bordered box
func renderGradientBox(content string, padX int, leftHex, rightHex string) string {
	w := lipgloss.Width(content) + padX*2
	top := lipgloss.NewStyle().Foreground(lipgloss.Color(leftHex)).Render("╭") + gradientLine("─", w, leftHex, rightHex) + lipgloss.NewStyle().Foreground(lipgloss.Color(rightHex)).Render("╮")
	bottom := lipgloss.NewStyle().Foreground(lipgloss.Color(leftHex)).Render("╰") + gradientLine("─", w, leftHex, rightHex) + lipgloss.NewStyle().Foreground(lipgloss.Color(rightHex)).Render("╯")
	left := lipgloss.NewStyle().Foreground(lipgloss.Color(leftHex)).Render("│")
	right := lipgloss.NewStyle().Foreground(lipgloss.Color(rightHex)).Render("│")
	middle := left + strings.Repeat(" ", padX) + content + strings.Repeat(" ", padX) + right
	return strings.Join([]string{top, middle, bottom}, "\n")
}

func gradientLine(ch string, width int, fromHex, toHex string) string {
	colors := gradientColors(fromHex, toHex, width)
	var b strings.Builder
	for i := 0; i < width; i++ {
		b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color(colors[i])).Render(ch))
	}
	return b.String()
}

func gradientText(text, leftHex, rightHex string) string {
	colors := gradientColors(leftHex, rightHex, len(text))
	var b strings.Builder
	idx := 0
	for _, ch := range text { // rune-safe
		b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color(colors[idx])).Bold(true).Render(string(ch)))
		idx++
	}
	return b.String()
}

func gradientColors(fromHex, toHex string, steps int) []string {
	r1, g1, b1 := hexToRGB(fromHex)
	r2, g2, b2 := hexToRGB(toHex)
	out := make([]string, steps)
	for i := 0; i < steps; i++ {
		if steps == 1 {
			out[i] = fromHex
			continue
		}
		t := float64(i) / float64(steps-1)
		r := int(float64(r1) + (float64(r2)-float64(r1))*t)
		g := int(float64(g1) + (float64(g2)-float64(g1))*t)
		b := int(float64(b1) + (float64(b2)-float64(b1))*t)
		out[i] = fmt.Sprintf("#%02x%02x%02x", r, g, b)
	}
	return out
}

func hexToRGB(hex string) (int, int, int) {
	h := strings.TrimPrefix(hex, "#")
	if len(h) != 6 {
		return 255, 255, 255
	}
	var r, g, b int
	fmt.Sscanf(h, "%02x%02x%02x", &r, &g, &b)
	return r, g, b
}

func (a App) viewAchievements() string {
	adaptiveColors := theme.NewAdaptiveColors(a.th)
	gradientColors := adaptiveColors.GetGradientColors()
	achGrad := gradientColors["banner"]

	title := gradientText("Achievements", achGrad[0], achGrad[1])

	var content strings.Builder
	content.WriteString(title + "\n\n")

	unlockedCount := a.achievements.GetUnlockedCount()
	totalCount := a.achievements.GetTotalCount()
	progressText := fmt.Sprintf("Unlocked: %d/%d\n\n", unlockedCount, totalCount)
	content.WriteString(a.styles.Status.Render(progressText))

	achievementOrder := []string{
		"first_win", "perfectionist", "speed_demon", "no_mistakes",
		"streak_master", "lunatic_legend", "daily_devotee", "century",
	}

	for _, id := range achievementOrder {
		if ach, ok := a.achievements.Achievements[id]; ok {
			var line string
			if ach.Unlocked {
				line = fmt.Sprintf("%s %s - %s",
					ach.Icon,
					lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#10b981")).Render(ach.Name),
					ach.Description)
			} else {
				progressBar := ""
				if ach.Target > 1 {
					progressBar = fmt.Sprintf(" [%d/%d]", ach.Progress, ach.Target)
				}
				line = fmt.Sprintf("%s %s - %s%s",
					lipgloss.NewStyle().Foreground(lipgloss.Color("#6b7280")).Render("🔒"),
					lipgloss.NewStyle().Foreground(lipgloss.Color("#9ca3af")).Render(ach.Name),
					lipgloss.NewStyle().Foreground(lipgloss.Color("#6b7280")).Render(ach.Description),
					progressBar)
			}
			content.WriteString(line + "\n")
		}
	}

	content.WriteString("\n" + a.styles.Status.Render("Press 'm' or Enter to return to menu"))

	panel := a.styles.Panel.Render(content.String())
	if a.width > 0 && a.height > 0 {
		return a.styles.App.Render(lipgloss.Place(a.width, a.height, lipgloss.Center, lipgloss.Center, panel))
	}
	return a.styles.App.Render(panel)
}

func (a App) viewLeaderboard() string {
	adaptiveColors := theme.NewAdaptiveColors(a.th)
	gradientColors := adaptiveColors.GetGradientColors()
	leaderGrad := gradientColors["banner"]

	title := gradientText("Leaderboard", leaderGrad[0], leaderGrad[1])

	var content strings.Builder
	content.WriteString(title + "\n\n")

	difficulties := []string{"Easy", "Normal", "Hard", "Lunatic"}
	for _, diff := range difficulties {
		diffColors := adaptiveColors.GetDifficultyColors()
		diffColor := diffColors[diff]
		diffHeader := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(diffColor)).Render(diff)
		content.WriteString(diffHeader + ":\n")

		leaderboard := a.stats.GetLeaderboard(diff, 5)
		if len(leaderboard) == 0 {
			content.WriteString(a.styles.Status.Render("  No records yet\n\n"))
		} else {
			for i, record := range leaderboard {
				medal := "  "
				switch i {
				case 0:
					medal = "🥇"
				case 1:
					medal = "🥈"
				case 2:
					medal = "🥉"
				}
				timeStr := stats.FormatTime(record.Time)
				line := fmt.Sprintf("%s %d. %s", medal, i+1, timeStr)
				if record.HintsUsed > 0 {
					line += fmt.Sprintf(" (%d hints)", record.HintsUsed)
				}
				content.WriteString(a.styles.Status.Render(line + "\n"))
			}
			content.WriteString("\n")
		}
	}

	content.WriteString(a.styles.Status.Render("Press 'm' or Enter to return to menu"))

	panel := a.styles.Panel.Render(content.String())
	if a.width > 0 && a.height > 0 {
		return a.styles.App.Render(lipgloss.Place(a.width, a.height, lipgloss.Center, lipgloss.Center, panel))
	}
	return a.styles.App.Render(panel)
}
