package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/jesseduffield/gocui"
	"github.com/jesseduffield/lazygit/pkg/gui"
	"github.com/jesseduffield/lazygit/pkg/gui/style"
	"github.com/jesseduffield/lazygit/pkg/integration"
	"github.com/jesseduffield/lazygit/pkg/secureexec"
)

// this program lets you manage integration tests in a TUI.

type App struct {
	tests   []*integration.Test
	itemIdx int
	testDir string
	editing bool
	g       *gocui.Gui
}

func (app *App) getCurrentTest() *integration.Test {
	if len(app.tests) > 0 {
		return app.tests[app.itemIdx]
	}
	return nil
}

func (app *App) refreshTests() {
	app.loadTests()
	app.g.Update(func(*gocui.Gui) error {
		listView, err := app.g.View("list")
		if err != nil {
			return err
		}

		listView.Clear()
		for _, test := range app.tests {
			fmt.Fprintln(listView, test.Name)
		}

		return nil
	})
}

func (app *App) loadTests() {
	tests, err := integration.LoadTests(app.testDir)
	if err != nil {
		log.Panicln(err)
	}

	app.tests = tests
	if app.itemIdx > len(app.tests)-1 {
		app.itemIdx = len(app.tests) - 1
	}
}

func main() {
	rootDir := integration.GetRootDirectory()
	testDir := filepath.Join(rootDir, "test", "integration")

	app := &App{testDir: testDir}
	app.loadTests()

	g, err := gocui.NewGui(gocui.OutputTrue, false, gocui.NORMAL, false, gui.RuneReplacements)
	if err != nil {
		log.Panicln(err)
	}

	g.Cursor = false

	app.g = g

	g.SetManagerFunc(app.layout)

	if err := g.SetKeybinding("list", nil, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		if app.itemIdx > 0 {
			app.itemIdx--
		}
		listView, err := g.View("list")
		if err != nil {
			return err
		}
		listView.FocusPoint(0, app.itemIdx)
		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 'q', gocui.ModNone, quit); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 'r', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=record go run test/runner/main.go %s", currentTest.Name))
		app.runSubprocess(cmd)

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=sandbox go run test/runner/main.go %s", currentTest.Name))
		app.runSubprocess(cmd)

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true go run test/runner/main.go %s", currentTest.Name))
		app.runSubprocess(cmd)

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 'u', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=updateSnapshot go run test/runner/main.go %s", currentTest.Name))
		app.runSubprocess(cmd)

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 't', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true SPEED=1 go run test/runner/main.go %s", currentTest.Name))
		app.runSubprocess(cmd)

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r %s/%s/test.json", app.testDir, currentTest.Name))
		if err := cmd.Run(); err != nil {
			return err
		}

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 'n', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		// need to duplicate that folder and then re-fetch our tests.
		dir := app.testDir + "/" + app.getCurrentTest().Name
		newDir := dir + "_Copy"

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("cp -r %s %s", dir, newDir))
		if err := cmd.Run(); err != nil {
			return err
		}

		app.loadTests()

		app.refreshTests()
		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 'm', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		app.editing = true
		if _, err := g.SetCurrentView("editor"); err != nil {
			return err
		}
		editorView, err := g.View("editor")
		if err != nil {
			return err
		}
		editorView.Clear()
		fmt.Fprint(editorView, currentTest.Name)

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("list", nil, 'd', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		dir := app.testDir + "/" + app.getCurrentTest().Name

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("rm -rf %s", dir))
		if err := cmd.Run(); err != nil {
			return err
		}

		app.refreshTests()

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("editor", nil, gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		currentTest := app.getCurrentTest()
		if currentTest == nil {
			return nil
		}

		app.editing = false
		if _, err := g.SetCurrentView("list"); err != nil {
			return err
		}

		editorView, err := g.View("editor")
		if err != nil {
			return err
		}

		dir := app.testDir + "/" + app.getCurrentTest().Name
		newDir := app.testDir + "/" + editorView.Buffer()

		cmd := secureexec.Command("sh", "-c", fmt.Sprintf("mv %s %s", dir, newDir))
		if err := cmd.Run(); err != nil {
			return err
		}

		editorView.Clear()

		app.refreshTests()
		return nil
	}); err != nil {
		log.Panicln(err)
	}

	if err := g.SetKeybinding("editor", nil, gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		app.editing = false
		if _, err := g.SetCurrentView("list"); err != nil {
			return err
		}

		return nil
	}); err != nil {
		log.Panicln(err)
	}

	err = g.MainLoop()
	g.Close()
	switch err {
	case gocui.ErrQuit:
		return
	default:
		log.Panicln(err)
	}
}

func (app *App) runSubprocess(cmd *exec.Cmd) {
	if err := gocui.Screen.Suspend(); err != nil {
		panic(err)
	}

	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	if err := cmd.Run(); err != nil {
		log.Println(err.Error())
	}
	cmd.Stdin = nil
	cmd.Stderr = nil
	cmd.Stdout = nil

	fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return"))
	fmt.Scanln() // wait for enter press

	if err := gocui.Screen.Resume(); err != nil {
		panic(err)
	}
}

func (app *App) layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	descriptionViewHeight := 7
	keybindingsViewHeight := 3
	editorViewHeight := 3
	if !app.editing {
		editorViewHeight = 0
	} else {
		descriptionViewHeight = 0
		keybindingsViewHeight = 0
	}
	g.Cursor = app.editing
	g.FgColor = gocui.ColorGreen
	listView, err := g.SetView("list", 0, 0, maxX-1, maxY-descriptionViewHeight-keybindingsViewHeight-editorViewHeight-1, 0)
	if err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		listView.Highlight = true
		listView.Clear()
		for _, test := range app.tests {
			fmt.Fprintln(listView, test.Name)
		}
		listView.Title = "Tests"
		listView.FgColor = gocui.ColorDefault
		if _, err := g.SetCurrentView("list"); err != nil {
			return err
		}
	}

	descriptionView, err := g.SetViewBeneath("description", "list", descriptionViewHeight)
	if err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		descriptionView.Title = "Test description"
		descriptionView.Wrap = true
		descriptionView.FgColor = gocui.ColorDefault
	}

	keybindingsView, err := g.SetViewBeneath("keybindings", "description", keybindingsViewHeight)
	if err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		keybindingsView.Title = "Keybindings"
		keybindingsView.Wrap = true
		keybindingsView.FgColor = gocui.ColorDefault
		fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, u: run test and update snapshots, r: record test, s: sandbox, o: open test config, n: duplicate test, m: rename test, d: delete test, t: run test at original speed")
	}

	editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight)
	if err != nil {
		if err.Error() != "unknown view" {
			return err
		}
		editorView.Title = "Enter Name"
		editorView.FgColor = gocui.ColorDefault
		editorView.Editable = true
	}

	currentTest := app.getCurrentTest()
	if currentTest == nil {
		return nil
	}

	descriptionView.Clear()
	fmt.Fprintf(descriptionView, "Speed: %f. %s", currentTest.Speed, currentTest.Description)

	if err := g.SetKeybinding("list", nil, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
		if app.itemIdx < len(app.tests)-1 {
			app.itemIdx++
		}

		listView, err := g.View("list")
		if err != nil {
			return err
		}
		listView.FocusPoint(0, app.itemIdx)
		return nil
	}); err != nil {
		log.Panicln(err)
	}

	return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	return gocui.ErrQuit
}
