package git_commands

import (
	"testing"

	"github.com/go-errors/errors"
	"github.com/jesseduffield/lazygit/pkg/commands/git_config"
	"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
	"github.com/jesseduffield/lazygit/pkg/config"
	"github.com/stretchr/testify/assert"
)

func TestEditFileCmdStrLegacy(t *testing.T) {
	type scenario struct {
		filename                  string
		configEditCommand         string
		configEditCommandTemplate string
		runner                    *oscommands.FakeCmdObjRunner
		getenv                    func(string) string
		gitConfigMockResponses    map[string]string
		test                      func(string, error)
	}

	scenarios := []scenario{
		{
			filename:                  "test",
			configEditCommand:         "",
			configEditCommandTemplate: "{{editor}} {{filename}}",
			runner: oscommands.NewFakeRunner(t).
				ExpectArgs([]string{"which", "vi"}, "", errors.New("error")),
			getenv: func(env string) string {
				return ""
			},
			gitConfigMockResponses: nil,
			test: func(cmdStr string, err error) {
				assert.EqualError(t, err, "No editor defined in config file, $GIT_EDITOR, $VISUAL, $EDITOR, or git config")
			},
		},
		{
			filename:                  "test",
			configEditCommand:         "nano",
			configEditCommandTemplate: "{{editor}} {{filename}}",
			runner:                    oscommands.NewFakeRunner(t),
			getenv: func(env string) string {
				return ""
			},
			gitConfigMockResponses: nil,
			test: func(cmdStr string, err error) {
				assert.NoError(t, err)
				assert.Equal(t, `nano "test"`, cmdStr)
			},
		},
		{
			filename:                  "test",
			configEditCommand:         "",
			configEditCommandTemplate: "{{editor}} {{filename}}",
			runner:                    oscommands.NewFakeRunner(t),
			getenv: func(env string) string {
				return ""
			},
			gitConfigMockResponses: map[string]string{"core.editor": "nano"},
			test: func(cmdStr string, err error) {
				assert.NoError(t, err)
				assert.Equal(t, `nano "test"`, cmdStr)
			},
		},
		{
			filename:                  "test",
			configEditCommand:         "",
			configEditCommandTemplate: "{{editor}} {{filename}}",
			runner:                    oscommands.NewFakeRunner(t),
			getenv: func(env string) string {
				if env == "VISUAL" {
					return "nano"
				}

				return ""
			},
			gitConfigMockResponses: nil,
			test: func(cmdStr string, err error) {
				assert.NoError(t, err)
				assert.Equal(t, `nano "test"`, cmdStr)
			},
		},
		{
			filename:                  "test",
			configEditCommand:         "",
			configEditCommandTemplate: "{{editor}} {{filename}}",
			runner:                    oscommands.NewFakeRunner(t),
			getenv: func(env string) string {
				if env == "EDITOR" {
					return "emacs"
				}

				return ""
			},
			gitConfigMockResponses: nil,
			test: func(cmdStr string, err error) {
				assert.NoError(t, err)
				assert.Equal(t, `emacs "test"`, cmdStr)
			},
		},
		{
			filename:                  "test",
			configEditCommand:         "",
			configEditCommandTemplate: "{{editor}} {{filename}}",
			runner: oscommands.NewFakeRunner(t).
				ExpectArgs([]string{"which", "vi"}, "/usr/bin/vi", nil),
			getenv: func(env string) string {
				return ""
			},
			gitConfigMockResponses: nil,
			test: func(cmdStr string, err error) {
				assert.NoError(t, err)
				assert.Equal(t, `vi "test"`, cmdStr)
			},
		},
		{
			filename:                  "file/with space",
			configEditCommand:         "",
			configEditCommandTemplate: "{{editor}} {{filename}}",
			runner: oscommands.NewFakeRunner(t).
				ExpectArgs([]string{"which", "vi"}, "/usr/bin/vi", nil),
			getenv: func(env string) string {
				return ""
			},
			gitConfigMockResponses: nil,
			test: func(cmdStr string, err error) {
				assert.NoError(t, err)
				assert.Equal(t, `vi "file/with space"`, cmdStr)
			},
		},
		{
			filename:                  "open file/at line",
			configEditCommand:         "vim",
			configEditCommandTemplate: "{{editor}} +{{line}} {{filename}}",
			runner:                    oscommands.NewFakeRunner(t),
			getenv: func(env string) string {
				return ""
			},
			gitConfigMockResponses: nil,
			test: func(cmdStr string, err error) {
				assert.NoError(t, err)
				assert.Equal(t, `vim +1 "open file/at line"`, cmdStr)
			},
		},
		{
			filename:                  "default edit command template",
			configEditCommand:         "vim",
			configEditCommandTemplate: "",
			runner:                    oscommands.NewFakeRunner(t),
			getenv: func(env string) string {
				return ""
			},
			gitConfigMockResponses: nil,
			test: func(cmdStr string, err error) {
				assert.NoError(t, err)
				assert.Equal(t, `vim +1 -- "default edit command template"`, cmdStr)
			},
		},
	}

	for _, s := range scenarios {
		userConfig := config.GetDefaultConfig()
		userConfig.OS.EditCommand = s.configEditCommand
		userConfig.OS.EditCommandTemplate = s.configEditCommandTemplate

		instance := buildFileCommands(commonDeps{
			runner:     s.runner,
			userConfig: userConfig,
			gitConfig:  git_config.NewFakeGitConfig(s.gitConfigMockResponses),
			getenv:     s.getenv,
		})

		s.test(instance.GetEditCmdStrLegacy(s.filename, 1))
		s.runner.CheckForMissingCalls()
	}
}

func TestEditFilesCmd(t *testing.T) {
	type scenario struct {
		filenames      []string
		osConfig       config.OSConfig
		expectedCmdStr string
		suspend        bool
	}

	scenarios := []scenario{
		{
			filenames:      []string{"test"},
			osConfig:       config.OSConfig{},
			expectedCmdStr: `vim -- "test"`,
			suspend:        true,
		},
		{
			filenames: []string{"test"},
			osConfig: config.OSConfig{
				Edit: "nano {{filename}}",
			},
			expectedCmdStr: `nano "test"`,
			suspend:        true,
		},
		{
			filenames: []string{"file/with space"},
			osConfig: config.OSConfig{
				EditPreset: "sublime",
			},
			expectedCmdStr: `subl -- "file/with space"`,
			suspend:        false,
		},
		{
			filenames: []string{"multiple", "files"},
			osConfig: config.OSConfig{
				EditPreset: "sublime",
			},
			expectedCmdStr: `subl -- "multiple" "files"`,
			suspend:        false,
		},
	}

	for _, s := range scenarios {
		userConfig := config.GetDefaultConfig()
		userConfig.OS = s.osConfig

		instance := buildFileCommands(commonDeps{
			userConfig: userConfig,
		})

		cmdStr, suspend := instance.GetEditCmdStr(s.filenames)
		assert.Equal(t, s.expectedCmdStr, cmdStr)
		assert.Equal(t, s.suspend, suspend)
	}
}

func TestEditFileAtLineCmd(t *testing.T) {
	type scenario struct {
		filename       string
		lineNumber     int
		osConfig       config.OSConfig
		expectedCmdStr string
		suspend        bool
	}

	scenarios := []scenario{
		{
			filename:       "test",
			lineNumber:     42,
			osConfig:       config.OSConfig{},
			expectedCmdStr: `vim +42 -- "test"`,
			suspend:        true,
		},
		{
			filename:   "test",
			lineNumber: 35,
			osConfig: config.OSConfig{
				EditAtLine: "nano +{{line}} {{filename}}",
			},
			expectedCmdStr: `nano +35 "test"`,
			suspend:        true,
		},
		{
			filename:   "file/with space",
			lineNumber: 12,
			osConfig: config.OSConfig{
				EditPreset: "sublime",
			},
			expectedCmdStr: `subl -- "file/with space":12`,
			suspend:        false,
		},
	}

	for _, s := range scenarios {
		userConfig := config.GetDefaultConfig()
		userConfig.OS = s.osConfig

		instance := buildFileCommands(commonDeps{
			userConfig: userConfig,
		})

		cmdStr, suspend := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber)
		assert.Equal(t, s.expectedCmdStr, cmdStr)
		assert.Equal(t, s.suspend, suspend)
	}
}

func TestEditFileAtLineAndWaitCmd(t *testing.T) {
	type scenario struct {
		filename       string
		lineNumber     int
		osConfig       config.OSConfig
		expectedCmdStr string
	}

	scenarios := []scenario{
		{
			filename:       "test",
			lineNumber:     42,
			osConfig:       config.OSConfig{},
			expectedCmdStr: `vim +42 -- "test"`,
		},
		{
			filename:   "file/with space",
			lineNumber: 12,
			osConfig: config.OSConfig{
				EditPreset: "sublime",
			},
			expectedCmdStr: `subl --wait -- "file/with space":12`,
		},
	}

	for _, s := range scenarios {
		userConfig := config.GetDefaultConfig()
		userConfig.OS = s.osConfig

		instance := buildFileCommands(commonDeps{
			userConfig: userConfig,
		})

		cmdStr := instance.GetEditAtLineAndWaitCmdStr(s.filename, s.lineNumber)
		assert.Equal(t, s.expectedCmdStr, cmdStr)
	}
}

func TestGuessDefaultEditor(t *testing.T) {
	type scenario struct {
		gitConfigMockResponses map[string]string
		getenv                 func(string) string
		expectedResult         string
	}

	scenarios := []scenario{
		{
			gitConfigMockResponses: nil,
			getenv: func(env string) string {
				return ""
			},
			expectedResult: "",
		},
		{
			gitConfigMockResponses: map[string]string{"core.editor": "nano"},
			getenv: func(env string) string {
				return ""
			},
			expectedResult: "nano",
		},
		{
			gitConfigMockResponses: map[string]string{"core.editor": "code -w"},
			getenv: func(env string) string {
				return ""
			},
			expectedResult: "code",
		},
		{
			gitConfigMockResponses: nil,
			getenv: func(env string) string {
				if env == "VISUAL" {
					return "emacs"
				}

				return ""
			},
			expectedResult: "emacs",
		},
		{
			gitConfigMockResponses: nil,
			getenv: func(env string) string {
				if env == "EDITOR" {
					return "bbedit -w"
				}

				return ""
			},
			expectedResult: "bbedit",
		},
	}

	for _, s := range scenarios {
		instance := buildFileCommands(commonDeps{
			gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
			getenv:    s.getenv,
		})

		assert.Equal(t, s.expectedResult, instance.guessDefaultEditor())
	}
}
