package git_commands

import (
	"testing"

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

func NewBranchCommandsWithRunner(runner *oscommands.FakeCmdObjRunner) *BranchCommands {
	builder := oscommands.NewDummyCmdObjBuilder(runner)
	return NewBranchCommands(utils.NewDummyCommon(), builder)
}

func TestBranchGetCommitDifferences(t *testing.T) {
	type scenario struct {
		testName          string
		runner            *oscommands.FakeCmdObjRunner
		expectedPushables string
		expectedPullables string
	}

	scenarios := []scenario{
		{
			"Can't retrieve pushable count",
			oscommands.NewFakeRunner(t).
				Expect("git rev-list @{u}..HEAD --count", "", errors.New("error")),
			"?", "?",
		},
		{
			"Can't retrieve pullable count",
			oscommands.NewFakeRunner(t).
				Expect("git rev-list @{u}..HEAD --count", "1\n", nil).
				Expect("git rev-list HEAD..@{u} --count", "", errors.New("error")),
			"?", "?",
		},
		{
			"Retrieve pullable and pushable count",
			oscommands.NewFakeRunner(t).
				Expect("git rev-list @{u}..HEAD --count", "1\n", nil).
				Expect("git rev-list HEAD..@{u} --count", "2\n", nil),
			"1", "2",
		},
	}

	for _, s := range scenarios {
		s := s
		t.Run(s.testName, func(t *testing.T) {
			instance := NewBranchCommandsWithRunner(s.runner)
			pushables, pullables := instance.GetCommitDifferences("HEAD", "@{u}")
			assert.EqualValues(t, s.expectedPushables, pushables)
			assert.EqualValues(t, s.expectedPullables, pullables)
			s.runner.CheckForMissingCalls()
		})
	}
}

func TestBranchNewBranch(t *testing.T) {
	runner := oscommands.NewFakeRunner(t).
		Expect(`git checkout -b "test" "master"`, "", nil)
	instance := NewBranchCommandsWithRunner(runner)

	assert.NoError(t, instance.New("test", "master"))
	runner.CheckForMissingCalls()
}

func TestBranchDeleteBranch(t *testing.T) {
	type scenario struct {
		testName string
		force    bool
		runner   *oscommands.FakeCmdObjRunner
		test     func(error)
	}

	scenarios := []scenario{
		{
			"Delete a branch",
			false,
			oscommands.NewFakeRunner(t).Expect(`git branch -d "test"`, "", nil),
			func(err error) {
				assert.NoError(t, err)
			},
		},
		{
			"Force delete a branch",
			true,
			oscommands.NewFakeRunner(t).Expect(`git branch -D "test"`, "", nil),
			func(err error) {
				assert.NoError(t, err)
			},
		},
	}

	for _, s := range scenarios {
		s := s
		t.Run(s.testName, func(t *testing.T) {
			instance := NewBranchCommandsWithRunner(s.runner)

			s.test(instance.Delete("test", s.force))
			s.runner.CheckForMissingCalls()
		})
	}
}

func TestBranchMerge(t *testing.T) {
	runner := oscommands.NewFakeRunner(t).
		Expect(`git merge --no-edit "test"`, "", nil)
	instance := NewBranchCommandsWithRunner(runner)

	assert.NoError(t, instance.Merge("test", MergeOpts{}))
	runner.CheckForMissingCalls()
}

func TestBranchCheckout(t *testing.T) {
	type scenario struct {
		testName string
		runner   *oscommands.FakeCmdObjRunner
		test     func(error)
		force    bool
	}

	scenarios := []scenario{
		{
			"Checkout",
			oscommands.NewFakeRunner(t).Expect(`git checkout "test"`, "", nil),
			func(err error) {
				assert.NoError(t, err)
			},
			false,
		},
		{
			"Checkout forced",
			oscommands.NewFakeRunner(t).Expect(`git checkout --force "test"`, "", nil),
			func(err error) {
				assert.NoError(t, err)
			},
			true,
		},
	}

	for _, s := range scenarios {
		s := s
		t.Run(s.testName, func(t *testing.T) {
			instance := NewBranchCommandsWithRunner(s.runner)
			s.test(instance.Checkout("test", CheckoutOptions{Force: s.force}))
			s.runner.CheckForMissingCalls()
		})
	}
}

func TestBranchGetBranchGraph(t *testing.T) {
	runner := oscommands.NewFakeRunner(t).ExpectGitArgs([]string{
		"log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--",
	}, "", nil)
	instance := NewBranchCommandsWithRunner(runner)
	_, err := instance.GetGraph("test")
	assert.NoError(t, err)
}

func TestBranchGetAllBranchGraph(t *testing.T) {
	runner := oscommands.NewFakeRunner(t).ExpectGitArgs([]string{
		"log", "--graph", "--all", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium",
	}, "", nil)
	instance := NewBranchCommandsWithRunner(runner)
	err := instance.AllBranchesLogCmdObj().Run()
	assert.NoError(t, err)
}

func TestBranchCurrentBranchName(t *testing.T) {
	type scenario struct {
		testName string
		runner   *oscommands.FakeCmdObjRunner
		test     func(string, string, error)
	}

	scenarios := []scenario{
		{
			"says we are on the master branch if we are",
			oscommands.NewFakeRunner(t).Expect(`git symbolic-ref --short HEAD`, "master", nil),
			func(name string, displayname string, err error) {
				assert.NoError(t, err)
				assert.EqualValues(t, "master", name)
				assert.EqualValues(t, "master", displayname)
			},
		},
		{
			"falls back to git `git branch --contains` if symbolic-ref fails",
			oscommands.NewFakeRunner(t).
				Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
				Expect(`git branch --contains`, "* master", nil),
			func(name string, displayname string, err error) {
				assert.NoError(t, err)
				assert.EqualValues(t, "master", name)
				assert.EqualValues(t, "master", displayname)
			},
		},
		{
			"handles a detached head",
			oscommands.NewFakeRunner(t).
				Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
				Expect(`git branch --contains`, "* (HEAD detached at 123abcd)", nil),
			func(name string, displayname string, err error) {
				assert.NoError(t, err)
				assert.EqualValues(t, "123abcd", name)
				assert.EqualValues(t, "(HEAD detached at 123abcd)", displayname)
			},
		},
		{
			"bubbles up error if there is one",
			oscommands.NewFakeRunner(t).
				Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")).
				Expect(`git branch --contains`, "", errors.New("error")),
			func(name string, displayname string, err error) {
				assert.Error(t, err)
				assert.EqualValues(t, "", name)
				assert.EqualValues(t, "", displayname)
			},
		},
	}

	for _, s := range scenarios {
		s := s
		t.Run(s.testName, func(t *testing.T) {
			instance := NewBranchCommandsWithRunner(s.runner)
			s.test(instance.CurrentBranchName())
			s.runner.CheckForMissingCalls()
		})
	}
}
