package git_commands

import (
	"testing"

	"github.com/go-errors/errors"
	"github.com/spf13/afero"
	"github.com/stretchr/testify/assert"
)

func mockResolveSymlinkFn(p string) (string, error) { return p, nil }

type Scenario struct {
	Name       string
	BeforeFunc func(fs afero.Fs)
	Path       string
	Expected   *RepoPaths
	Err        error
}

func TestGetRepoPathsAux(t *testing.T) {
	scenarios := []Scenario{
		{
			Name: "typical case",
			BeforeFunc: func(fs afero.Fs) {
				// setup for main worktree
				_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
			},
			Path: "/path/to/repo",
			Expected: &RepoPaths{
				currentPath:        "/path/to/repo",
				worktreePath:       "/path/to/repo",
				worktreeGitDirPath: "/path/to/repo/.git",
				repoPath:           "/path/to/repo",
				repoGitDirPath:     "/path/to/repo/.git",
				repoName:           "repo",
			},
			Err: nil,
		},
		{
			Name: "linked worktree",
			BeforeFunc: func(fs afero.Fs) {
				// setup for linked worktree
				_ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree1", 0o755)
				_ = afero.WriteFile(fs, "/path/to/repo/worktree1/.git", []byte("gitdir: /path/to/repo/.git/worktrees/worktree1"), 0o644)
			},
			Path: "/path/to/repo/worktree1",
			Expected: &RepoPaths{
				currentPath:        "/path/to/repo/worktree1",
				worktreePath:       "/path/to/repo/worktree1",
				worktreeGitDirPath: "/path/to/repo/.git/worktrees/worktree1",
				repoPath:           "/path/to/repo",
				repoGitDirPath:     "/path/to/repo/.git",
				repoName:           "repo",
			},
			Err: nil,
		},
		{
			Name: "worktree with trailing separator in path",
			BeforeFunc: func(fs afero.Fs) {
				// setup for linked worktree
				_ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree1", 0o755)
				_ = afero.WriteFile(fs, "/path/to/repo/worktree1/.git", []byte("gitdir: /path/to/repo/.git/worktrees/worktree1/"), 0o644)
			},
			Path: "/path/to/repo/worktree1",
			Expected: &RepoPaths{
				currentPath:        "/path/to/repo/worktree1",
				worktreePath:       "/path/to/repo/worktree1",
				worktreeGitDirPath: "/path/to/repo/.git/worktrees/worktree1",
				repoPath:           "/path/to/repo",
				repoGitDirPath:     "/path/to/repo/.git",
				repoName:           "repo",
			},
			Err: nil,
		},
		{
			Name: "worktree .git file missing gitdir directive",
			BeforeFunc: func(fs afero.Fs) {
				_ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree2", 0o755)
				_ = afero.WriteFile(fs, "/path/to/repo/worktree2/.git", []byte("blah"), 0o644)
			},
			Path:     "/path/to/repo/worktree2",
			Expected: nil,
			Err:      errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2: /path/to/repo/worktree2/.git is a file which suggests we are in a submodule or a worktree but the file's contents do not contain a gitdir pointing to the actual .git directory"),
		},
		{
			Name: "worktree .git file gitdir directive points to a non-existing directory",
			BeforeFunc: func(fs afero.Fs) {
				_ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree2", 0o755)
				_ = afero.WriteFile(fs, "/path/to/repo/worktree2/.git", []byte("gitdir: /nonexistant"), 0o644)
			},
			Path:     "/path/to/repo/worktree2",
			Expected: nil,
			Err:      errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2. /nonexistant does not exist"),
		},
		{
			Name: "submodule",
			BeforeFunc: func(fs afero.Fs) {
				_ = fs.MkdirAll("/path/to/repo/.git/modules/submodule1", 0o755)
				_ = afero.WriteFile(fs, "/path/to/repo/submodule1/.git", []byte("gitdir: /path/to/repo/.git/modules/submodule1"), 0o644)
			},
			Path: "/path/to/repo/submodule1",
			Expected: &RepoPaths{
				currentPath:        "/path/to/repo/submodule1",
				worktreePath:       "/path/to/repo/submodule1",
				worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1",
				repoPath:           "/path/to/repo/submodule1",
				repoGitDirPath:     "/path/to/repo/.git/modules/submodule1",
				repoName:           "submodule1",
			},
			Err: nil,
		},
		{
			Name: "submodule in nested directory",
			BeforeFunc: func(fs afero.Fs) {
				_ = fs.MkdirAll("/path/to/repo/.git/modules/my/submodule1", 0o755)
				_ = afero.WriteFile(fs, "/path/to/repo/my/submodule1/.git", []byte("gitdir: /path/to/repo/.git/modules/my/submodule1"), 0o644)
			},
			Path: "/path/to/repo/my/submodule1",
			Expected: &RepoPaths{
				currentPath:        "/path/to/repo/my/submodule1",
				worktreePath:       "/path/to/repo/my/submodule1",
				worktreeGitDirPath: "/path/to/repo/.git/modules/my/submodule1",
				repoPath:           "/path/to/repo/my/submodule1",
				repoGitDirPath:     "/path/to/repo/.git/modules/my/submodule1",
				repoName:           "submodule1",
			},
			Err: nil,
		},
		{
			Name: "submodule git dir not under .git/modules",
			BeforeFunc: func(fs afero.Fs) {
				_ = fs.MkdirAll("/random/submodule1", 0o755)
				_ = afero.WriteFile(fs, "/path/to/repo/my/submodule1/.git", []byte("gitdir: /random/submodule1"), 0o644)
			},
			Path:     "/path/to/repo/my/submodule1",
			Expected: nil,
			Err:      errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/my/submodule1: the path '/random/submodule1' is not under `worktrees` or `modules` directories"),
		},
	}

	for _, s := range scenarios {
		s := s
		t.Run(s.Name, func(t *testing.T) {
			fs := afero.NewMemMapFs()

			// prepare the filesystem for the scenario
			s.BeforeFunc(fs)

			// run the function with the scenario path
			repoPaths, err := getRepoPathsAux(fs, mockResolveSymlinkFn, s.Path)

			// check the error and the paths
			if s.Err != nil {
				assert.Error(t, err)
				assert.EqualError(t, err, s.Err.Error())
			} else {
				assert.Nil(t, err)
				assert.Equal(t, s.Expected, repoPaths)
			}
		})
	}
}
