package tui

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"testing"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/dundee/gdu/v5/internal/common"
	"github.com/dundee/gdu/v5/internal/testanalyze"
	"github.com/dundee/gdu/v5/internal/testapp"
	"github.com/dundee/gdu/v5/internal/testdev"
	"github.com/dundee/gdu/v5/internal/testdir"
	"github.com/dundee/gdu/v5/pkg/analyze"
	"github.com/dundee/gdu/v5/pkg/device"
	"github.com/dundee/gdu/v5/pkg/fs"
	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"
	"github.com/stretchr/testify/assert"
)

func init() {
	log.SetLevel(log.WarnLevel)
}

func TestFooter(t *testing.T) {
	app, simScreen := testapp.CreateTestAppWithSimScreen(15, 15)
	defer simScreen.Fini()

	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	dir := &analyze.Dir{
		File: &analyze.File{
			Name:  "xxx",
			Size:  5,
			Usage: 4096,
		},
		BasePath:  ".",
		ItemCount: 2,
	}

	file := &analyze.File{
		Name:   "yyy",
		Size:   2,
		Usage:  4096,
		Parent: dir,
	}
	dir.Files = fs.Files{file}

	ui.currentDir = dir
	ui.showDir()
	ui.pages.HidePage("progress")

	ui.footerLabel.Draw(simScreen)
	simScreen.Show()

	b, _, _ := simScreen.GetContents()

	// printScreen(simScreen)

	text := []byte(" Total disk    usage: 4.0 KiB Apparent size: 2 B Items: 1")
	for i, r := range b {
		if i >= len(text) {
			break
		}
		assert.Equal(t, string(text[i]), string(r.Bytes[0]), fmt.Sprintf("Index: %d", i))
	}
}

func TestUpdateProgress(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, false, false, false)
	done := ui.Analyzer.GetDone()
	done.Broadcast()
	ui.updateProgress(ui.Analyzer, done)
}

func TestUpdateProgressShowsCancellationHint(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(false).(*testapp.MockedApp)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, false, false, false)
	ui.progress = ui.header
	done := make(common.SignalGroup)
	finished := make(chan struct{})
	go func() {
		defer close(finished)
		ui.updateProgress(ui.Analyzer, done)
	}()

	if !assert.Eventually(t, func() bool {
		return len(app.GetUpdateDraws()) > 0
	}, time.Second, 10*time.Millisecond) {
		done.Broadcast()
		<-finished
		return
	}
	done.Broadcast()
	<-finished

	draws := app.GetUpdateDraws()
	draws[0]()
	assert.Contains(t, ui.progress.GetText(false), "Press Ctrl+C to stop scanning and keep results")
}

func TestSetShowDiskProgressBar(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, false, false, false)

	ui.SetShowDiskProgressBar(true)
	assert.True(t, ui.showDiskProgressBar)

	ui.SetShowDiskProgressBar(false)
	assert.False(t, ui.showDiskProgressBar)
}

func TestDeviceSelectedSetsCurrentDeviceSize(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(false)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, true, true, true, false)
	ui.Analyzer = &testanalyze.MockedAnalyzer{}
	ui.done = make(chan struct{})
	ui.SetIgnoreDirPaths([]string{"/xxx"})
	err := ui.ListDevices(getDevicesInfoMock())
	assert.Nil(t, err)

	ui.deviceItemSelected(1, 0)

	// currentDeviceSize must be set to the selected device's Size before analysis starts.
	assert.Equal(t, int64(1e12), ui.currentDeviceSize)

	<-ui.done
	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}
}

func TestHelp(t *testing.T) {
	app, simScreen := testapp.CreateTestAppWithSimScreen(50, 50)
	defer simScreen.Fini()

	ui := CreateUI(app, simScreen, &bytes.Buffer{}, true, true, false, false)
	ui.showHelp()

	assert.True(t, ui.pages.HasPage("help"))

	ui.help.Draw(simScreen)
	simScreen.Show()

	// printScreen(simScreen)

	b, _, _ := simScreen.GetContents()

	cells := b[607 : 607+9]

	text := []byte("directory")
	for i, r := range cells {
		assert.Equal(t, text[i], r.Bytes[0])
	}
}

func TestHelpBw(t *testing.T) {
	app, simScreen := testapp.CreateTestAppWithSimScreen(50, 50)
	defer simScreen.Fini()

	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)
	ui.showHelp()
	ui.help.Draw(simScreen)
	simScreen.Show()

	// printScreen(simScreen)

	b, _, _ := simScreen.GetContents()

	cells := b[607 : 607+9]

	text := []byte("directory")
	for i, r := range cells {
		assert.Equal(t, text[i], r.Bytes[0])
	}
}

func TestAppRun(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(false)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	err := ui.StartUILoop()

	assert.Nil(t, err)
}

func TestUILoopReturnsWithPendingSignal(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	assert.NoError(t, simScreen.Init())
	defer simScreen.Fini()
	screen := &failOnceSignalScreen{
		Screen:    simScreen,
		attempted: make(chan struct{}),
		fail:      true,
	}
	app := &signalLifecycleApp{
		MockedApp:       testapp.CreateMockedApp(false).(*testapp.MockedApp),
		signalAttempted: screen.attempted,
	}
	ui := CreateUI(app, screen, &bytes.Buffer{}, false, true, false, false)
	signals := make(chan os.Signal, 1)
	signals <- os.Interrupt

	result := make(chan error, 1)
	go func() {
		result <- ui.runUILoop(signals)
	}()

	select {
	case err := <-result:
		assert.NoError(t, err)
	case <-time.After(time.Second):
		t.Fatal("UI loop did not stop its signal listener")
	}
}

func TestAppRunWithErr(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	err := ui.StartUILoop()

	assert.Equal(t, "Fail", err.Error())
}

func TestRescanDir(t *testing.T) {
	parentDir := &analyze.Dir{
		File: &analyze.File{
			Name: "parent",
		},
		Files: make([]fs.Item, 0, 1),
	}
	currentDir := &analyze.Dir{
		File: &analyze.File{
			Name:   "sub",
			Parent: parentDir,
		},
	}

	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()
	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)
	ui.done = make(chan struct{})
	ui.Analyzer = &testanalyze.MockedAnalyzer{}
	ui.currentDir = currentDir
	ui.topDir = parentDir
	ui.rescanDir()

	<-ui.done // wait for analyzer

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.Equal(t, "test_dir", ui.currentDir.GetName())
	assert.Equal(t, parentDir, ui.currentDir.GetParent())

	assert.Equal(t, 5, ui.table.GetRowCount())
	assert.Contains(t, ui.table.GetCell(0, 0).Text, "/..")
	assert.Contains(t, ui.table.GetCell(1, 0).Text, "ddd")
}

func TestDirSelected(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, true, false, false)
	ui.done = make(chan struct{})

	ui.fileItemSelected(0, 0)

	assert.Equal(t, 3, ui.table.GetRowCount())
	assert.Contains(t, ui.table.GetCell(0, 0).Text, "/..")
	assert.Contains(t, ui.table.GetCell(1, 0).Text, "subnested")
}

func TestFileSelected(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, true, true, true)

	ui.fileItemSelected(0, 0) // does nothing, since it's a file

	assert.Equal(t, 4, ui.table.GetRowCount())
	assert.Contains(t, ui.table.GetCell(1, 0).Text, "ccc")
}

func TestSelectedWithoutCurrentDir(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, true, true, false, false)

	ui.fileItemSelected(1, 0)

	assert.Nil(t, ui.currentDir)
}

func TestBeforeDraw(t *testing.T) {
	screen := tcell.NewSimulationScreen("UTF-8")
	err := screen.Init()

	assert.Nil(t, err)

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, screen, &bytes.Buffer{}, false, true, false, false)

	for _, f := range ui.app.(*testapp.MockedApp).BeforeDraws {
		assert.False(t, f(screen))
	}
}

func TestIgnorePaths(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.SetIgnoreDirPaths([]string{"/aaa", "/bbb"})

	assert.True(t, ui.ShouldDirBeIgnored("aaa", "/aaa"))
	assert.True(t, ui.ShouldDirBeIgnored("bbb", "/bbb"))
	assert.False(t, ui.ShouldDirBeIgnored("ccc", "/ccc"))
}

func TestConfirmDeletion(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, true, true, true)

	ui.table.Select(1, 0)
	ui.confirmDeletion(ActionDelete)

	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmDeletionBW(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, false, true, true)

	ui.table.Select(1, 0)
	ui.confirmDeletion(ActionDelete)

	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmEmpty(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, false, true, true)

	ui.table.Select(1, 0)
	ui.confirmDeletion(ActionEmpty)

	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmEmptyMarked(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, false, true, true)

	ui.table.Select(1, 0)
	ui.markedRows[1] = struct{}{}
	ui.confirmDeletion(ActionEmpty)

	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmDeletionMarked(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, true, true, true)

	ui.table.Select(1, 0)
	ui.markedRows[1] = struct{}{}
	ui.confirmDeletion(ActionDelete)

	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmDeletionMarkedBW(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, false, true, true)

	ui.table.Select(1, 0)
	ui.markedRows[1] = struct{}{}
	ui.confirmDeletion(ActionDelete)

	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestDeleteSelected(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.done = make(chan struct{})

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)

	ui.deleteSelected(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.NoDirExists(t, "test_dir/nested")
}

func TestDeleteSelectedInParallel(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.done = make(chan struct{})
	ui.SetDeleteInParallel()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)

	ui.deleteSelected(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.NoDirExists(t, "test_dir/nested")
}

func TestDeleteSelectedInBackground(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, true, true, false)
	ui.remover = testanalyze.ItemFromDirWithSleep
	ui.done = make(chan struct{})
	ui.SetDeleteInBackground()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)

	ui.deleteSelected(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.NoDirExists(t, "test_dir/nested")
}

func TestMoveToTrashSelectedInBackground(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, true, true, false)
	ui.done = make(chan struct{})
	ui.SetDeleteInBackground()
	var trashed string
	ui.trasher = func(dir, item fs.Item) error {
		trashed = item.GetName()
		dir.RemoveFile(item)
		return nil
	}

	assert.Equal(t, 1, ui.table.GetRowCount())
	ui.table.Select(0, 0)
	ui.deleteSelected(ActionMoveToTrash)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.Equal(t, "nested", trashed)
	assert.Equal(t, 0, ui.table.GetRowCount())
}

func TestDeleteSelectedInBackgroundAndParallel(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, true, true, false)
	ui.remover = testanalyze.ItemFromDirWithSleep
	ui.done = make(chan struct{})
	ui.SetDeleteInBackground()
	ui.SetDeleteInParallel()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)

	ui.deleteSelected(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.NoDirExists(t, "test_dir/nested")
}

func TestDeleteSelectedInBackgroundBW(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.done = make(chan struct{})
	ui.SetDeleteInBackground()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)

	ui.deleteSelected(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.NoDirExists(t, "test_dir/nested")
}

func TestEmptyDirInBackground(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, true, true, false)
	ui.done = make(chan struct{})
	ui.SetDeleteInBackground()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)

	ui.deleteSelected(ActionEmpty)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.DirExists(t, "test_dir/nested")
	assert.NoDirExists(t, "test_dir/nested/subnested")
}

func TestEmptyFileInBackground(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, true, true, false)
	ui.done = make(chan struct{})
	ui.SetDeleteInBackground()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.fileItemSelected(0, 0) // nested
	ui.table.Select(2, 0)

	ui.deleteSelected(ActionEmpty)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.DirExists(t, "test_dir/nested")
	assert.FileExists(t, "test_dir/nested/file2")

	f, err := os.Open("test_dir/nested/file2")
	assert.Nil(t, err)
	info, err := f.Stat()
	assert.Nil(t, err)
	assert.Equal(t, int64(0), info.Size())
}

func TestDeleteSelectedWithErr(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.remover = testanalyze.ItemFromDirWithErr

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)

	ui.delete(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.True(t, ui.pages.HasPage("error"))
	assert.DirExists(t, "test_dir/nested")
}

func TestDeleteSelectedInBackgroundWithErr(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.SetDeleteInBackground()
	ui.remover = testanalyze.ItemFromDirWithSleepAndErr

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)

	ui.delete(ActionDelete)

	<-ui.done

	// change the status
	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	// wait for status to be removed
	time.Sleep(500 * time.Millisecond)

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.True(t, ui.pages.HasPage("error"))
	assert.DirExists(t, "test_dir/nested")
}

func TestDeleteMarkedWithErr(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.remover = testanalyze.ItemFromDirWithErr

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)
	ui.markedRows[0] = struct{}{}

	ui.deleteMarked(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.True(t, ui.pages.HasPage("error"))
	assert.DirExists(t, "test_dir/nested")
}

func TestDeleteMarkedInBackground(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.SetDeleteInBackground()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.fileItemSelected(0, 0) // nested

	ui.markedRows[1] = struct{}{} // subnested
	ui.markedRows[2] = struct{}{} // file2

	ui.deleteMarked(ActionDelete)

	<-ui.done // wait for deletion of subnested
	<-ui.done // wait for deletion of file2

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.DirExists(t, "test_dir/nested")
	assert.NoDirExists(t, "test_dir/nested/subnested")
	assert.NoFileExists(t, "test_dir/nested/file2")
}

func TestDeleteMarkedInBackgroundWithStorage(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.SetAnalyzer(analyze.CreateStoredAnalyzer("/tmp/badger"))
	ui.SetDeleteInBackground()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.fileItemSelected(0, 0) // nested

	ui.markedRows[1] = struct{}{} // subnested
	ui.markedRows[2] = struct{}{} // file2

	ui.deleteMarked(ActionDelete)

	<-ui.done // wait for deletion of subnested
	<-ui.done // wait for deletion of file2

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.DirExists(t, "test_dir/nested")
	assert.NoDirExists(t, "test_dir/nested/subnested")
	assert.NoFileExists(t, "test_dir/nested/file2")
}

func TestDeleteMarkedInBackgroundWithStorageAndParallel(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.SetAnalyzer(analyze.CreateStoredAnalyzer("/tmp/badger"))
	ui.SetDeleteInBackground()
	ui.SetDeleteInParallel()

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.fileItemSelected(0, 0) // nested

	ui.markedRows[1] = struct{}{} // subnested
	ui.markedRows[2] = struct{}{} // file2

	ui.deleteMarked(ActionDelete)

	<-ui.done // wait for deletion of subnested
	<-ui.done // wait for deletion of file2

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.DirExists(t, "test_dir/nested")
	assert.NoDirExists(t, "test_dir/nested/subnested")
	assert.NoFileExists(t, "test_dir/nested/file2")
}

func TestDeleteMarkedInBackgroundWithErr(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.SetDeleteInBackground()
	ui.remover = testanalyze.ItemFromDirWithErr

	assert.Equal(t, 1, ui.table.GetRowCount())

	ui.table.Select(0, 0)
	ui.markedRows[0] = struct{}{}

	ui.deleteMarked(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.True(t, ui.pages.HasPage("error"))
	assert.DirExists(t, "test_dir/nested")
}

func TestShowErr(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, true, true, false, false)

	ui.showErr("Something went wrong", errors.New("error"))

	assert.True(t, ui.pages.HasPage("error"))
}

func TestShowErrBW(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.showErr("Something went wrong", errors.New("error"))

	assert.True(t, ui.pages.HasPage("error"))
}

func TestMin(t *testing.T) {
	assert.Equal(t, 2, min(2, 5))
	assert.Equal(t, 3, min(4, 3))
}

func TestSetStyles(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	opts := []Option{}
	opts = append(opts, func(ui *UI) {
		ui.SetHeaderHidden()
	})

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false, opts...)

	ui.SetSelectedBackgroundColor(tcell.ColorRed)
	ui.SetSelectedTextColor(tcell.ColorRed)
	ui.SetMarkedTextColor(tcell.ColorRed)
	ui.SetMarkedBackgroundColor(tcell.ColorRed)
	ui.SetFooterTextColor("red")
	ui.SetFooterBackgroundColor("red")
	ui.SetFooterNumberColor("red")
	ui.SetHeaderTextColor("red")
	ui.SetHeaderBackgroundColor("red")
	ui.SetResultRowDirectoryColor("red")
	ui.SetResultRowNumberColor("red")

	assert.Equal(t, ui.selectedBackgroundColor, tcell.ColorRed)
	assert.Equal(t, ui.selectedTextColor, tcell.ColorRed)
	assert.Equal(t, ui.markedTextColor, tcell.ColorRed)
	assert.Equal(t, ui.markedBackgroundColor, tcell.ColorRed)
	assert.Equal(t, ui.footerTextColor, "red")
	assert.Equal(t, ui.footerBackgroundColor, "red")
	assert.Equal(t, ui.footerNumberColor, "red")
	assert.Equal(t, ui.headerTextColor, "red")
	assert.Equal(t, ui.headerBackgroundColor, "red")
	assert.Equal(t, ui.headerHidden, true)
	assert.Equal(t, ui.resultRow.DirectoryColor, "red")
	assert.Equal(t, ui.resultRow.NumberColor, "red")
}

func TestSetCurrentItemNameMaxLen(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.SetCurrentItemNameMaxLen(5)

	assert.Equal(t, ui.currentItemNameMaxLen, 5)
}

func TestUseOldSizeBar(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.UseOldSizeBar()

	assert.Equal(t, ui.useOldSizeBar, true)
}

func TestSetShowBarPercentage(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.SetShowBarPercentage()

	assert.Equal(t, ui.showBarPercentage, true)
}

func TestSetShowItemCount(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.SetShowItemCount()

	assert.Equal(t, ui.showItemCount, true)
}

func TestSetShowMTime(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.SetShowMTime()

	assert.Equal(t, ui.showMtime, true)
}

func TestNoDelete(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.SetNoDelete()

	assert.Equal(t, ui.noDelete, true)
}

func TestNoSpawnShell(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.SetNoSpawnShell()

	assert.Equal(t, ui.noSpawnShell, true)
}

func TestNoViewFile(t *testing.T) {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, false, true, false, false)

	ui.SetNoViewFile()

	assert.Equal(t, ui.noViewFile, true)
}

// nolint: unused // Why: for debugging
func printScreen(simScreen tcell.SimulationScreen) {
	b, _, _ := simScreen.GetContents()

	for i, r := range b {
		if string(r.Bytes) != " " {
			println(i, string(r.Bytes))
		}
	}
}

func getDevicesInfoMock() device.DevicesInfoGetter {
	item := &device.Device{
		Name:       "/dev/root",
		MountPoint: "test_dir",
		Size:       1e12,
		Free:       1e6,
	}
	item2 := &device.Device{
		Name:       "/dev/boot",
		MountPoint: "/boot",
		Size:       1e6,
		Free:       1e3,
	}

	mock := testdev.DevicesInfoGetterMock{}
	mock.Devices = []*device.Device{item, item2}
	return mock
}

func getAnalyzedPathMockedApp(t *testing.T, useColors, apparentSize, mockedAnalyzer bool) *UI {
	simScreen := testapp.CreateSimScreen()
	defer simScreen.Fini()

	app := testapp.CreateMockedApp(true)
	ui := CreateUI(app, simScreen, &bytes.Buffer{}, useColors, apparentSize, false, false)

	if mockedAnalyzer {
		ui.Analyzer = &testanalyze.MockedAnalyzer{}
	}
	ui.done = make(chan struct{})
	err := ui.AnalyzePath("test_dir", nil)
	assert.Nil(t, err)

	<-ui.done // wait for analyzer

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.Equal(t, "test_dir", ui.currentDir.GetName())

	return ui
}

func TestConfirmDeletionSelectedButtonOrder(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, true, true, true)

	ui.table.Select(1, 0)
	ui.confirmDeletionSelected(ActionDelete)

	// Verify confirmation page is created
	assert.True(t, ui.pages.HasPage("confirm"))
}

func selectConfirmationYes(t *testing.T, ui *UI) {
	t.Helper()
	_, primitive := ui.pages.GetFrontPage()
	modal, ok := primitive.(*tview.Modal)
	if !assert.True(t, ok) {
		return
	}
	var setFocus func(tview.Primitive)
	setFocus = func(p tview.Primitive) {
		p.Focus(setFocus)
	}
	modal.SetFocus(1)
	modal.Focus(setFocus)
	modal.InputHandler()(tcell.NewEventKey(tcell.KeyEnter, 0, 0), setFocus)
}

func TestConfirmMoveToTrashSelectedYes(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, true, true, false)
	ui.done = make(chan struct{})
	var trashed string
	ui.trasher = func(dir, item fs.Item) error {
		trashed = item.GetName()
		dir.RemoveFile(item)
		return nil
	}
	ui.table.Select(0, 0)
	selected := ui.table.GetCell(0, 0).GetReference().(fs.Item).GetName()

	ui.confirmDeletionSelected(ActionMoveToTrash)
	selectConfirmationYes(t, ui)
	<-ui.done

	assert.Equal(t, selected, trashed)
}

func TestConfirmMoveToTrashMarkedYes(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, true, true, false)
	ui.done = make(chan struct{})
	var trashed string
	ui.trasher = func(dir, item fs.Item) error {
		trashed = item.GetName()
		dir.RemoveFile(item)
		return nil
	}
	ui.markedRows[0] = struct{}{}

	ui.confirmDeletionMarked(ActionMoveToTrash)
	selectConfirmationYes(t, ui)
	<-ui.done

	assert.Equal(t, "nested", trashed)
}

func TestConfirmDeletionSelectedSafeDefault(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.done = make(chan struct{})

	assert.Equal(t, 1, ui.table.GetRowCount())
	ui.table.Select(0, 0)

	// Create confirmation dialog
	ui.confirmDeletionSelected(ActionDelete)

	// Verify that the confirmation dialog exists with safer defaults
	assert.DirExists(t, "test_dir/nested")
	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmDeletionButtonIndexMapping(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.done = make(chan struct{})
	ui.askBeforeDelete = false // Skip confirmation for direct testing

	assert.Equal(t, 1, ui.table.GetRowCount())
	ui.table.Select(0, 0)

	// Test that deletion still works when explicitly called
	ui.deleteSelected(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.NoDirExists(t, "test_dir/nested")
}

func TestConfirmEmptySelectedSafeDefault(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, true, true, true)

	ui.table.Select(1, 0)
	ui.confirmDeletionSelected(ActionEmpty)

	// Verify empty confirmation dialog is created safely
	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmDeletionMarkedSafeDefault(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, true, true, true)

	ui.table.Select(1, 0)
	ui.markedRows[1] = struct{}{}
	ui.confirmDeletionMarked(ActionDelete)

	// Verify marked deletion confirmation dialog is created safely
	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmEmptyMarkedSafeDefault(t *testing.T) {
	ui := getAnalyzedPathMockedApp(t, false, true, true)

	ui.table.Select(1, 0)
	ui.markedRows[1] = struct{}{}
	ui.confirmDeletionMarked(ActionEmpty)

	// Verify marked empty confirmation dialog is created safely
	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestSaferConfirmationPreventDataLoss(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)

	assert.Equal(t, 1, ui.table.GetRowCount())
	ui.table.Select(0, 0)

	// Test that creating confirmation dialog doesn't accidentally trigger deletion
	ui.confirmDeletionSelected(ActionDelete)
	ui.confirmDeletionSelected(ActionEmpty) // empty

	// Directory should still exist - no accidental deletion
	assert.DirExists(t, "test_dir/nested")
	assert.True(t, ui.pages.HasPage("confirm"))
}

func TestConfirmDeletionSelectedCase1(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.done = make(chan struct{})

	assert.Equal(t, 1, ui.table.GetRowCount())
	ui.table.Select(0, 0)

	// Test case 1 branch (yes button at index 1) by directly calling deleteSelected
	ui.deleteSelected(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.NoDirExists(t, "test_dir/nested")
}

func TestConfirmDeletionMarkedCase1(t *testing.T) {
	fin := testdir.CreateTestDir()
	defer fin()

	ui := getAnalyzedPathMockedApp(t, false, true, false)
	ui.done = make(chan struct{})

	ui.fileItemSelected(0, 0)     // nested
	ui.markedRows[1] = struct{}{} // subnested

	// Test case 1 branch (yes button at index 1) by directly calling deleteMarked
	ui.deleteMarked(ActionDelete)

	<-ui.done

	for _, f := range ui.app.(*testapp.MockedApp).GetUpdateDraws() {
		f()
	}

	assert.NoDirExists(t, "test_dir/nested/subnested")
}
