package screens

import (
	"errors"
	"fmt"
	"sync/atomic"

	"charm.land/bubbles/v2/key"
	"charm.land/bubbles/v2/spinner"
	tea "charm.land/bubbletea/v2"

	"charm.land/huh/v2"

	"git.secluded.site/keld/internal/restic"
	"git.secluded.site/keld/internal/theme"
	"git.secluded.site/keld/internal/ui"
)

// manualEntryValue is the sentinel option value returned by the
// snapshot select when the user chooses "Enter ID manually…".
const manualEntryValue = "__manual__"

// snapshotPhase tracks where the snapshot screen is in its
// internal flow.
type snapshotPhase int

const (
	phaseSnapshotLoading   snapshotPhase = iota // spinner while fetching
	phaseSnapshotSelecting                      // huh Select with results
	phaseSnapshotManual                         // huh Input fallback
)

// SnapshotLoader fetches snapshots from the repository. Injected at
// construction so tests can provide a fake.
type SnapshotLoader func() ([]restic.Snapshot, error)

// snapshotsLoadedMsg carries the result of an async snapshot load.
type snapshotsLoadedMsg struct {
	gen       int64
	snapshots []restic.Snapshot
	err       error
}

// Snapshot is a Screen adapter for selecting a restic snapshot ID.
// It loads snapshots asynchronously, presents a filterable list, and
// falls back to manual text entry on failure or user choice.
type Snapshot struct {
	loader  SnapshotLoader
	styles  *theme.Styles
	spinner spinner.Model

	phase snapshotPhase
	gen   int64 // generation counter for stale-result protection

	// Selecting phase.
	selectForm *huh.Form
	selected   string
	snapshots  []restic.Snapshot // cached for rebuild after back-nav

	// Manual entry phase.
	manualForm *huh.Form
	entered    string
	notice     string // displayed above the manual input

	// Result.
	value     string
	selection string
}

// NewSnapshot creates a snapshot selection screen. If loader is nil,
// the screen goes directly to manual entry (for when no repository
// is configured and the caller knows it upfront).
func NewSnapshot(loader SnapshotLoader, styles *theme.Styles) *Snapshot {
	sp := spinner.New(spinner.WithSpinner(spinner.Dot))
	sp.Style = sp.Style.Foreground(styles.Accent)

	return &Snapshot{
		loader:  loader,
		styles:  styles,
		spinner: sp,
	}
}

// Init starts the async snapshot load (or goes straight to manual
// entry if no loader is configured). Each call bumps the generation
// counter so stale results from prior loads are ignored.
func (s *Snapshot) Init() tea.Cmd {
	s.selection = ""
	s.value = ""

	if s.loader == nil {
		s.phase = phaseSnapshotManual
		s.entered = ""
		s.buildManualForm()
		return s.manualForm.Init()
	}

	s.phase = phaseSnapshotLoading
	gen := atomic.AddInt64(&s.gen, 1)

	return tea.Batch(s.spinner.Tick, func() tea.Msg {
		snaps, err := s.loader()
		return snapshotsLoadedMsg{gen: gen, snapshots: snaps, err: err}
	})
}

// Update handles messages across all phases.
func (s *Snapshot) Update(msg tea.Msg) (ui.Screen, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.BackgroundColorMsg:
		s.spinner.Style = s.spinner.Style.Foreground(s.styles.Accent)
		// Rebuild whichever form is active.
		switch s.phase {
		case phaseSnapshotSelecting:
			// Rebuild would lose scroll position; skip for now.
		case phaseSnapshotManual:
			s.buildManualForm()
			return s, s.manualForm.Init()
		}
		return s, nil

	case tea.KeyPressMsg:
		if msg.Code == tea.KeyEscape {
			return s.handleEsc()
		}

	case snapshotsLoadedMsg:
		return s.handleLoaded(msg)
	}

	// Delegate to the active phase.
	switch s.phase {
	case phaseSnapshotLoading:
		return s.updateLoading(msg)
	case phaseSnapshotSelecting:
		return s.updateSelecting(msg)
	case phaseSnapshotManual:
		return s.updateManual(msg)
	}

	return s, nil
}

// handleEsc processes Esc across all phases.
func (s *Snapshot) handleEsc() (ui.Screen, tea.Cmd) {
	switch s.phase {
	case phaseSnapshotLoading:
		return s, ui.BackCmd
	case phaseSnapshotSelecting:
		return s, ui.BackCmd
	case phaseSnapshotManual:
		// If we came from the select phase (user chose manual entry),
		// Esc should go back to the select. Otherwise, Esc backs out
		// of the whole screen.
		if s.selectForm != nil {
			s.phase = phaseSnapshotSelecting
			s.rebuildSelectForm()
			return s, s.selectForm.Init()
		}
		return s, ui.BackCmd
	}
	return s, ui.BackCmd
}

// handleLoaded processes the async snapshot load result.
func (s *Snapshot) handleLoaded(msg snapshotsLoadedMsg) (ui.Screen, tea.Cmd) {
	// Ignore stale results from a prior generation.
	if msg.gen != atomic.LoadInt64(&s.gen) {
		return s, nil
	}

	if msg.err != nil {
		if errors.Is(msg.err, restic.ErrNoRepo) {
			// Silent fallback — no notice.
			s.switchToManual("")
		} else {
			s.switchToManual(fmt.Sprintf("Could not list snapshots: %v", msg.err))
		}
		return s, s.manualForm.Init()
	}

	if len(msg.snapshots) == 0 {
		s.switchToManual("No snapshots found in repository.")
		return s, s.manualForm.Init()
	}

	s.buildSelectForm(msg.snapshots)
	s.phase = phaseSnapshotSelecting
	return s, s.selectForm.Init()
}

// updateLoading forwards messages to the spinner during the loading phase.
func (s *Snapshot) updateLoading(msg tea.Msg) (ui.Screen, tea.Cmd) {
	var cmd tea.Cmd
	s.spinner, cmd = s.spinner.Update(msg)
	return s, cmd
}

// updateSelecting forwards messages to the huh select form.
func (s *Snapshot) updateSelecting(msg tea.Msg) (ui.Screen, tea.Cmd) {
	if s.selectForm == nil {
		return s, nil
	}

	model, cmd := s.selectForm.Update(msg)
	if f, ok := model.(*huh.Form); ok {
		s.selectForm = f
	}

	if s.selectForm.State == huh.StateCompleted {
		if s.selected == manualEntryValue {
			s.switchToManual("")
			return s, s.manualForm.Init()
		}
		s.value = s.selected
		s.selection = s.selected
		return s, ui.DoneCmd
	}

	return s, cmd
}

// updateManual forwards messages to the huh manual input form.
func (s *Snapshot) updateManual(msg tea.Msg) (ui.Screen, tea.Cmd) {
	if s.manualForm == nil {
		return s, nil
	}

	model, cmd := s.manualForm.Update(msg)
	if f, ok := model.(*huh.Form); ok {
		s.manualForm = f
	}

	if s.manualForm.State == huh.StateCompleted {
		s.value = s.entered
		s.selection = s.entered
		return s, ui.DoneCmd
	}

	return s, cmd
}

// buildSelectForm constructs the huh Select from loaded snapshots.
func (s *Snapshot) buildSelectForm(snapshots []restic.Snapshot) {
	s.snapshots = snapshots
	opts := make([]huh.Option[string], 0, len(snapshots)+1)
	for _, snap := range snapshots {
		opts = append(opts, huh.NewOption(restic.FormatSnapshotLine(snap), snap.ShortID))
	}
	opts = append(opts, huh.NewOption("Enter ID manually…", manualEntryValue))

	s.selected = ""
	sel := huh.NewSelect[string]().
		Options(opts...).
		Filtering(true).
		Value(&s.selected)

	s.selectForm = huh.NewForm(
		huh.NewGroup(sel),
	).WithTheme(s.styles.Huh).WithShowHelp(false)
}

// rebuildSelectForm reconstructs the select form from the cached
// snapshots. Called when navigating back from manual entry to
// ensure the form has fresh internal state (filter, cursor, focus).
func (s *Snapshot) rebuildSelectForm() {
	if len(s.snapshots) > 0 {
		s.buildSelectForm(s.snapshots)
	}
}

// buildManualForm constructs the huh Input for manual snapshot ID entry.
// It does not reset s.entered so that callers rebuilding the form for
// theme changes preserve the user's input. Callers that start a fresh
// entry (Init, switchToManual) reset s.entered explicitly.
func (s *Snapshot) buildManualForm() {
	input := huh.NewInput().
		Title("Snapshot ID").
		Description("Supports snapshotID:subfolder syntax, or \"latest\".").
		Placeholder("e.g. latest or a1b2c3d4").
		Value(&s.entered).
		Validate(huh.ValidateNotEmpty())

	s.manualForm = huh.NewForm(
		huh.NewGroup(input),
	).WithTheme(s.styles.Huh).WithShowHelp(false)
}

// switchToManual transitions to the manual entry phase with an
// optional notice message.
func (s *Snapshot) switchToManual(notice string) {
	s.phase = phaseSnapshotManual
	s.notice = notice
	s.entered = ""
	s.buildManualForm()
}

// View renders the current phase.
func (s *Snapshot) View() string {
	switch s.phase {
	case phaseSnapshotLoading:
		return s.spinner.View() + " Loading snapshots…"
	case phaseSnapshotSelecting:
		if s.selectForm == nil {
			return ""
		}
		return s.selectForm.View()
	case phaseSnapshotManual:
		var view string
		if s.notice != "" {
			view = s.notice + "\n\n"
		}
		if s.manualForm != nil {
			view += s.manualForm.View()
		}
		return view
	}
	return ""
}

// Title returns the screen's display title.
func (s *Snapshot) Title() string { return "Select a snapshot" }

// KeyBindings returns bindings for the help bar.
func (s *Snapshot) KeyBindings() []key.Binding {
	switch s.phase {
	case phaseSnapshotSelecting:
		return []key.Binding{
			key.NewBinding(key.WithKeys("↑/↓"), key.WithHelp("↑/↓", "navigate")),
			key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
		}
	default:
		return []key.Binding{
			key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "confirm")),
		}
	}
}

// Selection returns the chosen snapshot ID for breadcrumb display,
// or "" if nothing has been selected yet.
func (s *Snapshot) Selection() string { return s.selection }

// Value returns the resolved snapshot ID.
func (s *Snapshot) Value() string { return s.value }
