// Package tui implements the interactive terminal UI for xetup.
// Phase 1 – configuration form (huh embedded in bubbletea).
// Phase 2 – live log view while PowerShell scripts run.
// Phase 3 – final summary.
package tui

import (
	"context"
	"fmt"
	"strings"
	"time"

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

	"git.xetup.x9.cz/x9/xetup/internal/config"
	"git.xetup.x9.cz/x9/xetup/internal/runner"
)

// --------------------------------------------------------------------------
// Phase
// --------------------------------------------------------------------------

type phase int

const (
	phaseForm phase = iota
	phaseRun
	phaseDone
)

// --------------------------------------------------------------------------
// Async message types (runner → bubbletea)
// --------------------------------------------------------------------------

type logMsg    runner.LogLine
type resultMsg runner.Result
type doneMsg   []runner.Result

// --------------------------------------------------------------------------
// Model
// --------------------------------------------------------------------------

// Model is the top-level bubbletea model.
type Model struct {
	phase  phase
	form   *huh.Form
	runCfg runner.RunConfig
	cfg    config.Config

	// form-bound values (huh writes here via pointer)
	pcName           string
	pcDesc           string
	productKey       string
	profileType      string
	selectedItemKeys []string

	// runner state
	r      *runner.Runner
	cancel context.CancelFunc
	msgCh  chan tea.Msg

	logs    []runner.LogLine
	results []runner.Result

	// terminal dimensions
	width  int
	height int
}

// NewModel returns the initial model pre-populated from cfg.
func NewModel(cfg config.Config, runCfg runner.RunConfig) Model {
	m := Model{
		cfg:         cfg,
		runCfg:      runCfg,
		pcName:      cfg.Deployment.PCName,
		pcDesc:      cfg.Deployment.PCDescription,
		productKey:  cfg.Activation.ProductKey,
		profileType: cfg.Deployment.ProfileType,
		width:       80,
		height:      24,
	}
	m.form = buildForm(&m)
	return m
}

// --------------------------------------------------------------------------
// Form construction
// --------------------------------------------------------------------------

func buildForm(m *Model) *huh.Form {
	allItems := runner.AllSelectableItems()
	opts := make([]huh.Option[string], len(allItems))
	for i, item := range allItems {
		opts[i] = huh.NewOption(item.Label, item.Key).Selected(true)
	}

	return huh.NewForm(
		huh.NewGroup(
			huh.NewInput().
				Title("Jmeno pocitace").
				Placeholder("napr. NB-KLIENT-01  (prazdne = neprejmenovat)").
				Value(&m.pcName),
			huh.NewInput().
				Title("Popis pocitace").
				Placeholder("napr. PC recepce").
				Value(&m.pcDesc),
			huh.NewInput().
				Title("Product Key").
				Placeholder("prazdne = OA3 / GVLK fallback").
				Value(&m.productKey),
			huh.NewSelect[string]().
				Title("Profil").
				Options(
					huh.NewOption("Default", "default"),
					huh.NewOption("Admin (Explorer, PS, Edge)", "admin"),
					huh.NewOption("User  (Explorer, Edge)", "user"),
				).
				Value(&m.profileType),
		),
		huh.NewGroup(
			huh.NewMultiSelect[string]().
				Title("Kroky a nastaveni (mezernikem odzaskrtnout)").
				Options(opts...).
				Value(&m.selectedItemKeys),
		),
	)
}

// --------------------------------------------------------------------------
// bubbletea interface
// --------------------------------------------------------------------------

// Init implements tea.Model.
func (m Model) Init() tea.Cmd {
	return m.form.Init()
}

// Update implements tea.Model.
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {

	case tea.WindowSizeMsg:
		m.width = msg.Width
		m.height = msg.Height

	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c":
			if m.cancel != nil {
				m.cancel()
			}
			return m, tea.Quit
		}
		if m.phase == phaseDone {
			return m, tea.Quit
		}

	// --- async runner messages ---

	case logMsg:
		m.logs = append(m.logs, runner.LogLine(msg))
		return m, readMsg(m.msgCh)

	case resultMsg:
		m.results = append(m.results, runner.Result(msg))
		return m, readMsg(m.msgCh)

	case doneMsg:
		m.results = []runner.Result(msg)
		m.phase = phaseDone
		return m, nil
	}

	// --- form phase ---
	if m.phase == phaseForm {
		form, cmd := m.form.Update(msg)
		if f, ok := form.(*huh.Form); ok {
			m.form = f
		}
		switch m.form.State {
		case huh.StateCompleted:
			return m.startRun()
		case huh.StateAborted:
			return m, tea.Quit
		}
		return m, cmd
	}

	return m, nil
}

// View implements tea.Model.
func (m Model) View() string {
	switch m.phase {
	case phaseForm:
		return m.viewForm()
	case phaseRun:
		return m.viewRun()
	case phaseDone:
		return m.viewDone()
	}
	return ""
}

// --------------------------------------------------------------------------
// Phase transitions
// --------------------------------------------------------------------------

// startRun applies form values and kicks off the runner goroutine.
func (m Model) startRun() (Model, tea.Cmd) {
	// persist form values into config
	m.cfg.Deployment.PCName = m.pcName
	m.cfg.Deployment.PCDescription = m.pcDesc
	m.cfg.Activation.ProductKey = m.productKey
	m.cfg.Deployment.ProfileType = m.profileType
	m.runCfg.ProfileType = m.profileType

	// Index selected keys for fast lookup
	selectedSet := make(map[string]bool, len(m.selectedItemKeys))
	for _, key := range m.selectedItemKeys {
		selectedSet[key] = true
	}

	// Build Features map: feature enabled = its composite key was selected
	features := make(config.Features)
	for _, item := range runner.AllSelectableItems() {
		if item.FeatureID == "" {
			continue
		}
		if features[item.StepID] == nil {
			features[item.StepID] = make(map[string]bool)
		}
		features[item.StepID][item.FeatureID] = selectedSet[item.Key]
	}
	m.cfg.Features = features

	// Determine which steps are enabled:
	// - step with features: enabled if at least one feature selected
	// - step without features: enabled if step key selected
	stepEnabled := make(map[string]bool)
	for _, item := range runner.AllSelectableItems() {
		if item.FeatureID == "" {
			stepEnabled[item.StepID] = selectedSet[item.Key]
		} else if selectedSet[item.Key] {
			// any feature selected enables the step
			stepEnabled[item.StepID] = true
		}
	}

	allSteps := runner.AllSteps()
	steps := make([]runner.Step, len(allSteps))
	for i, s := range allSteps {
		s.Enabled = stepEnabled[s.ID]
		steps[i] = s
	}

	// set up channel and start runner
	ch := make(chan tea.Msg, 200)
	m.msgCh = ch
	m.phase = phaseRun

	ctx, cancel := context.WithCancel(context.Background())
	m.cancel = cancel

	rc := m.runCfg
	r := runner.New(
		rc,
		func(l runner.LogLine) { ch <- logMsg(l) },
		func(res runner.Result) { ch <- resultMsg(res) },
	)
	m.r = r

	go func() {
		results := r.Run(ctx, steps)
		ch <- doneMsg(results)
	}()

	return m, readMsg(ch)
}

// readMsg returns a tea.Cmd that blocks until the next message arrives on ch.
func readMsg(ch <-chan tea.Msg) tea.Cmd {
	return func() tea.Msg {
		return <-ch
	}
}

// --------------------------------------------------------------------------
// Styles
// --------------------------------------------------------------------------

var (
	sTitle  = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#58a6ff"))
	sOK     = lipgloss.NewStyle().Foreground(lipgloss.Color("#2ea043"))
	sError  = lipgloss.NewStyle().Foreground(lipgloss.Color("#da3633"))
	sWarn   = lipgloss.NewStyle().Foreground(lipgloss.Color("#d29922"))
	sStep   = lipgloss.NewStyle().Foreground(lipgloss.Color("#58a6ff"))
	sMuted  = lipgloss.NewStyle().Foreground(lipgloss.Color("#888888"))
	sBold   = lipgloss.NewStyle().Bold(true)
)

// --------------------------------------------------------------------------
// Views
// --------------------------------------------------------------------------

func (m Model) viewForm() string {
	header := sTitle.Render("xetup") + "  " + sMuted.Render("Windows deployment – vyplnte konfiguraci")
	return header + "\n\n" + m.form.View()
}

func (m Model) viewRun() string {
	allSteps := runner.AllSteps()
	doneCount := len(m.results)
	totalCount := len(allSteps)

	var sb strings.Builder

	// header bar
	sb.WriteString(sTitle.Render("xetup") + "  ")
	if doneCount < totalCount {
		s := allSteps[doneCount]
		sb.WriteString(sBold.Render(fmt.Sprintf("krok %d/%d", doneCount+1, totalCount)))
		sb.WriteString("  " + s.Num + " - " + s.Name)
	} else {
		sb.WriteString(sBold.Render("dokoncuji..."))
	}
	sb.WriteByte('\n')
	sb.WriteString(strings.Repeat("─", clamp(m.width, 20, 90)))
	sb.WriteByte('\n')

	// log lines – show last N to fit terminal
	maxLines := m.height - 5
	if maxLines < 5 {
		maxLines = 5
	}
	start := 0
	if len(m.logs) > maxLines {
		start = len(m.logs) - maxLines
	}
	for _, l := range m.logs[start:] {
		sb.WriteString(styledLogLine(l))
		sb.WriteByte('\n')
	}

	sb.WriteString(sMuted.Render("\nCtrl+C pro zastaveni"))
	return sb.String()
}

func (m Model) viewDone() string {
	var sb strings.Builder

	sb.WriteString(sTitle.Render("xetup  ") + sBold.Render("hotovo") + "\n")
	sb.WriteString(strings.Repeat("─", clamp(m.width, 20, 90)) + "\n\n")

	okCount, errCount, skipCount := 0, 0, 0
	for _, res := range m.results {
		var prefix string
		switch res.Status {
		case "OK":
			okCount++
			prefix = sOK.Render("  OK      ")
		case "ERROR":
			errCount++
			prefix = sError.Render("  ERROR   ")
		default:
			skipCount++
			prefix = sMuted.Render("  SKIPPED ")
		}
		sb.WriteString(prefix + res.Step.Num + " - " + res.Step.Name)
		if res.Elapsed > 0 {
			sb.WriteString(sMuted.Render(fmt.Sprintf("  (%s)", res.Elapsed.Round(time.Second))))
		}
		sb.WriteByte('\n')
	}

	sb.WriteString("\n" + strings.Repeat("─", clamp(m.width, 20, 90)) + "\n")
	summary := fmt.Sprintf("OK: %d  CHYBY: %d  PRESKOCENO: %d", okCount, errCount, skipCount)
	if errCount > 0 {
		sb.WriteString(sError.Render(summary))
	} else {
		sb.WriteString(sOK.Render(summary))
	}
	sb.WriteString(sMuted.Render("\n\nStisknete libovolnou klavesu pro ukonceni..."))
	return sb.String()
}

// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------

func styledLogLine(l runner.LogLine) string {
	switch l.Level {
	case "OK":
		return sOK.Render(l.Text)
	case "ERROR":
		return sError.Render(l.Text)
	case "WARN":
		return sWarn.Render(l.Text)
	case "STEP":
		return sStep.Render(l.Text)
	default:
		return sMuted.Render(l.Text)
	}
}

func clamp(v, lo, hi int) int {
	if v < lo {
		return lo
	}
	if v > hi {
		return hi
	}
	return v
}
