package bookmarks

import (
	"slices"
	"strings"
	"testing"

	tea "charm.land/bubbletea/v2"
	"github.com/idursun/jjui/internal/jj"
	"github.com/idursun/jjui/internal/ui/intents"
	"github.com/idursun/jjui/internal/ui/layout"
	"github.com/idursun/jjui/internal/ui/render"
	"github.com/idursun/jjui/test"
	"github.com/stretchr/testify/assert"
)

func TestDistanceMap(t *testing.T) {
	selectedCommitId := "x"
	changeIds := []string{"a", "x", "b", "c", "d"}
	distanceMap := calcDistanceMap(selectedCommitId, changeIds)
	assert.Equal(t, 0, distanceMap["x"])
	assert.Equal(t, -1, distanceMap["a"])
	assert.Equal(t, 1, distanceMap["b"])
	assert.Equal(t, 2, distanceMap["c"])
	assert.Equal(t, 3, distanceMap["d"])
	assert.Equal(t, 0, distanceMap["nonexistent"])
}

func Test_Sorting_MoveCommands(t *testing.T) {
	items := []item{
		item{name: "move feature", dist: 5, priority: moveCommand},
		item{name: "move main", dist: 1, priority: moveCommand},
		item{name: "move very-old-feature", dist: 15, priority: moveCommand},
		item{name: "move backwards", dist: -2, priority: moveCommand},
	}
	slices.SortFunc(items, itemSorter)
	var sorted []string
	for _, i := range items {
		sorted = append(sorted, i.name)
	}
	assert.Equal(t, []string{"move main", "move feature", "move very-old-feature", "move backwards"}, sorted)
}

func Test_Sorting_MixedCommands(t *testing.T) {
	items := []item{
		item{name: "move very-old-feature", dist: 2, priority: moveCommand},
		item{name: "move main", dist: 0, priority: moveCommand},
		item{name: "delete very-old-feature", dist: 3, priority: deleteCommand},
		item{name: "delete main", dist: 0, priority: deleteCommand},
	}
	slices.SortFunc(items, itemSorter)
	var sorted []string
	for _, i := range items {
		sorted = append(sorted, i.name)
	}
	assert.Equal(t, []string{"move main", "move very-old-feature", "delete main", "delete very-old-feature"}, sorted)
}

// TestBookmarks_ZIndex_RendersAboveMainContent verifies that the bookmarks
// overlay renders at z-index >= render.ZMenuBorder. This ensures the bookmarks
// operations menu renders above the main revision list content.
func TestBookmarks_ZIndex_RendersAboveMainContent(t *testing.T) {
	commandRunner := test.NewTestCommandRunner(t)
	commandRunner.Expect(jj.GitRemoteList()).SetOutput([]byte("origin"))
	commandRunner.Expect(jj.BookmarkListAll()).SetOutput([]byte(""))
	commandRunner.Expect(jj.BookmarkListMovable("abc123")).SetOutput([]byte(""))

	commit := &jj.Commit{ChangeId: "abc123", CommitId: "commit123"}
	op := NewModel(test.NewTestContext(commandRunner), commit, []string{"commit123"})
	test.SimulateModel(op, op.Init())

	dl := render.NewDisplayContext()
	box := layout.Box{R: layout.Rect(0, 0, 100, 40)}
	dl.AddDraw(box.R, strings.Repeat("x", box.R.Dx()*box.R.Dy()), render.ZBase)
	op.ViewRect(dl, box)

	rendered := dl.RenderToString(box.R.Dx(), box.R.Dy())
	assert.Contains(t, rendered, "Remotes:", "bookmarks overlay should remain visible above base content")
}

func Test_FilterIntentPressedTwice_ExecutesShortcut(t *testing.T) {
	commandRunner := test.NewTestCommandRunner(t)
	commandRunner.Expect(jj.GitRemoteList()).SetOutput([]byte(""))
	commandRunner.Expect(jj.BookmarkListAll()).SetOutput([]byte(""))
	commandRunner.Expect(jj.BookmarkListMovable("abc123")).SetOutput([]byte(`
main;.;false;false;false;86
`))
	commandRunner.Expect(jj.BookmarkMove("abc123", "main"))
	defer commandRunner.Verify()

	commit := &jj.Commit{ChangeId: "abc123", CommitId: "commit123"}
	op := NewModel(test.NewTestContext(commandRunner), commit, []string{"commit123"})
	test.SimulateModel(op, op.Init())
	_ = test.RenderImmediate(op, 100, 40)

	// First press applies the category filter; second press executes its shortcut.
	test.SimulateModel(op, func() tea.Msg { return intents.BookmarksFilter{Kind: intents.BookmarksFilterMove} })
	test.SimulateModel(op, func() tea.Msg { return intents.BookmarksFilter{Kind: intents.BookmarksFilterMove} })
}

func Test_FilterIntentPressedTwice_ExecutesMainShortcutWhenCursorIsElsewhere(t *testing.T) {
	commandRunner := test.NewTestCommandRunner(t)
	commandRunner.Expect(jj.GitRemoteList()).SetOutput([]byte(""))
	commandRunner.Expect(jj.BookmarkListAll()).SetOutput([]byte(""))
	commandRunner.Expect(jj.BookmarkListMovable("abc123")).SetOutput([]byte(`
some-other-bookmark;.;false;false;false;nearby
main;.;false;false;false;mainCommit
`))
	commandRunner.Expect(jj.BookmarkMove("abc123", "main"))
	defer commandRunner.Verify()

	commit := &jj.Commit{ChangeId: "abc123", CommitId: "commit123"}
	op := NewModel(test.NewTestContext(commandRunner), commit, []string{"commit123", "nearby", "mainCommit"})
	test.SimulateModel(op, op.Init())
	_ = test.RenderImmediate(op, 100, 40)

	test.SimulateModel(op, func() tea.Msg { return intents.BookmarksFilter{Kind: intents.BookmarksFilterMove} })
	assertSelectedItem(t, op, "move 'some-other-bookmark' to abc123")

	test.SimulateModel(op, func() tea.Msg { return intents.BookmarksFilter{Kind: intents.BookmarksFilterMove} })
}

func Test_FilterEditing_AcceptsPasteMsg(t *testing.T) {
	commandRunner := test.NewTestCommandRunner(t)
	commandRunner.Expect(jj.GitRemoteList()).SetOutput([]byte(""))
	defer commandRunner.Verify()

	commit := &jj.Commit{ChangeId: "abc123", CommitId: "commit123"}
	op := NewModel(test.NewTestContext(commandRunner), commit, []string{"commit123"})

	test.SimulateModel(op, func() tea.Msg { return intents.BookmarksOpenFilter{} })
	test.SimulateModel(op, func() tea.Msg { return tea.PasteMsg{Content: "track feature"} })

	assert.Equal(t, "track feature", op.filterInput.Value())
}

func assertSelectedItem(t *testing.T, op *Model, want string) {
	t.Helper()

	selected, ok := op.selectedItem()
	assert.True(t, ok)
	assert.Equal(t, want, selected.name)
}
