package screens

import (
	"strings"
	"testing"

	tea "charm.land/bubbletea/v2"

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

// testStyles returns a *theme.Styles for testing. Dark mode is used
// because it's the session default.
func testStyles() *theme.Styles {
	s := theme.New(true)
	return &s
}

func testItems() []MenuItem {
	return []MenuItem{
		{Label: "backup", Hotkey: 'b'},
		{Label: "restore", Hotkey: 'r'},
		{Label: "snapshots", Hotkey: 's'},
	}
}

func TestMenuTitle(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())
	if got := m.Title(); got != "Select a command" {
		t.Errorf("Title() = %q, want %q", got, "Select a command")
	}
}

func TestMenuKeyBindings(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())
	bindings := m.KeyBindings()

	if len(bindings) == 0 {
		t.Fatal("KeyBindings() returned no bindings")
	}

	// Should include bindings for navigation and selection.
	helpKeys := make(map[string]bool)
	for _, b := range bindings {
		helpKeys[b.Help().Key] = true
	}

	for _, want := range []string{"↑/↓", "enter"} {
		if !helpKeys[want] {
			t.Errorf("KeyBindings() missing help key %q, got %v", want, helpKeys)
		}
	}
}

func TestMenuSelectionEmpty(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())
	if got := m.Selection(); got != "" {
		t.Errorf("Selection() before interaction = %q, want empty", got)
	}
}

func TestMenuEnterSelects(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())

	// Cursor starts at 0 ("backup"). Press enter.
	updated, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
	menu := updated.(*Menu)

	if cmd == nil {
		t.Fatal("expected DoneCmd on enter")
	}
	// Verify the cmd produces a DoneMsg.
	msg := cmd()
	if _, ok := msg.(ui.DoneMsg); !ok {
		t.Errorf("cmd produced %T, want ui.DoneMsg", msg)
	}

	if got := menu.Selection(); got != "backup" {
		t.Errorf("Selection() = %q, want %q", got, "backup")
	}
}

func TestMenuHotkeySelects(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())

	// Press 'r' — should jump to "restore" and select it.
	updated, cmd := m.Update(tea.KeyPressMsg{Code: 'r', Text: "r"})
	menu := updated.(*Menu)

	if cmd == nil {
		t.Fatal("expected DoneCmd on hotkey")
	}
	msg := cmd()
	if _, ok := msg.(ui.DoneMsg); !ok {
		t.Errorf("cmd produced %T, want ui.DoneMsg", msg)
	}

	if got := menu.Selection(); got != "restore" {
		t.Errorf("Selection() = %q, want %q", got, "restore")
	}
}

func TestMenuEscReturnsBack(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())

	_, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEscape})

	if cmd == nil {
		t.Fatal("expected BackCmd on Esc")
	}
	msg := cmd()
	if _, ok := msg.(ui.BackMsg); !ok {
		t.Errorf("cmd produced %T, want ui.BackMsg", msg)
	}
}

func TestMenuCursorNavigation(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())

	// Start at 0. Move down twice.
	updated, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyDown})
	updated, _ = updated.(*Menu).Update(tea.KeyPressMsg{Code: tea.KeyDown})
	menu := updated.(*Menu)

	// Select to verify cursor is at index 2 ("snapshots").
	updated, _ = menu.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
	menu = updated.(*Menu)
	if got := menu.Selection(); got != "snapshots" {
		t.Errorf("after two downs, Selection() = %q, want %q", got, "snapshots")
	}
}

func TestMenuCursorClampsAtBounds(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())

	// Try moving up past the top — should clamp at 0.
	updated, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyUp})
	menu := updated.(*Menu)

	updated, _ = menu.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
	menu = updated.(*Menu)
	if got := menu.Selection(); got != "backup" {
		t.Errorf("after up at top, Selection() = %q, want %q", got, "backup")
	}
}

func TestMenuCursorClampsAtBottom(t *testing.T) {
	t.Parallel()

	items := []MenuItem{{Label: "one", Hotkey: 'o'}}
	m := NewMenu(items, testStyles())

	// Move down past the bottom — should clamp at last item.
	updated, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyDown})
	menu := updated.(*Menu)

	updated, _ = menu.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
	menu = updated.(*Menu)
	if got := menu.Selection(); got != "one" {
		t.Errorf("after down past end, Selection() = %q, want %q", got, "one")
	}
}

func TestMenuViewShowsCursor(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())
	view := m.View()

	if !strings.Contains(view, theme.Cursor) {
		t.Errorf("View() should contain cursor %q, got:\n%s", theme.Cursor, view)
	}
}

func TestMenuViewNoHelpText(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())
	view := m.View()

	// The old menu rendered its own help string. The session now
	// provides the help bar, so the menu view must not include it.
	if strings.Contains(view, "navigate") {
		t.Errorf("View() should not contain help text, got:\n%s", view)
	}
	if strings.Contains(view, "q to quit") {
		t.Errorf("View() should not contain quit help, got:\n%s", view)
	}
}

func TestMenuItemValueOverridesLabel(t *testing.T) {
	t.Parallel()

	items := []MenuItem{
		{Label: "display name", Hotkey: 'd', Value: "actual-value"},
	}
	m := NewMenu(items, testStyles())

	updated, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
	menu := updated.(*Menu)

	if got := menu.Selection(); got != "actual-value" {
		t.Errorf("Selection() = %q, want %q (Value should override Label)", got, "actual-value")
	}
}

func TestMenuQKeyNoSpecialBehavior(t *testing.T) {
	t.Parallel()

	// Without a quit item, 'q' should do nothing special — it's not
	// a hotkey for any of these items.
	m := NewMenu(testItems(), testStyles())

	updated, cmd := m.Update(tea.KeyPressMsg{Code: 'q', Text: "q"})
	menu := updated.(*Menu)

	if cmd != nil {
		t.Error("'q' should not produce any command when not a hotkey")
	}
	if menu.Selection() != "" {
		t.Error("'q' should not select anything when not a hotkey")
	}
}

func TestMenuKJNavigation(t *testing.T) {
	t.Parallel()

	m := NewMenu(testItems(), testStyles())

	// 'j' should move down (vim-style).
	updated, _ := m.Update(tea.KeyPressMsg{Code: 'j', Text: "j"})
	menu := updated.(*Menu)

	updated, _ = menu.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
	menu = updated.(*Menu)
	if got := menu.Selection(); got != "restore" {
		t.Errorf("after 'j', Selection() = %q, want %q", got, "restore")
	}
}

func TestMenuRuneHotkey(t *testing.T) {
	t.Parallel()

	// A hotkey that is a multi-byte UTF-8 rune (like 'ñ') should
	// still be matched correctly via rune comparison.
	items := []MenuItem{{Label: "año", Hotkey: 'ñ'}}
	m := NewMenu(items, testStyles())

	updated, cmd := m.Update(tea.KeyPressMsg{Code: 0, Text: "ñ"})
	menu := updated.(*Menu)

	if cmd == nil {
		t.Fatal("expected DoneCmd for multi-byte rune hotkey")
	}
	if menu.Selection() != "año" {
		t.Errorf("Selection() = %q, want %q", menu.Selection(), "año")
	}
}
