package git

import (
	"bytes"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestConfig(t *testing.T) {
	// Taken from https://git-scm.com/docs/git-config#_example.
	raw := `
# Core variables
[core]
	; Don't trust file modes
	filemode = false

# Our diff algorithm
[diff]
	external = /usr/local/bin/diff-wrapper
	renames = true

[branch "devel"]
	remote = origin
	merge = refs/heads/devel

# Proxy settings
[core]
	gitProxy="ssh" for "kernel.org"
	gitProxy=default-proxy ; for the rest

[include]
	path = /path/to/foo.inc ; include by absolute path
	path = foo.inc ; find "foo.inc" relative to the current file
	path = ~/foo.inc ; find "foo.inc" in your $HOME directory

; include if $GIT_DIR is /path/to/foo/.git
[includeIf "gitdir:/path/to/foo/.git"]
	path = /path/to/foo.inc

; include for all repositories inside /path/to/group
[includeIf "gitdir:/path/to/group/"]
	path = /path/to/foo.inc

; include for all repositories inside $HOME/to/group
[includeIf "gitdir:~/to/group/"]
	path = /path/to/foo.inc

; relative paths are always relative to the including
; file (if the condition is true); their location is not
; affected by the condition
[includeIf "gitdir:/path/to/group/"]
	path = foo.inc

; include only if we are in a worktree where foo-branch is
; currently checked out
[includeIf "onbranch:foo-branch"]
	path = foo.inc

; include only if a remote with the given URL exists (note
; that such a URL may be provided later in a file or in a
; file read after this file is read, as seen in this example)
[includeIf "hasconfig:remote.*.url:https://example.com/**"]
	path = foo.inc
[remote "origin"]
	url = https://example.com/git
`

	c, err := newConfig()
	require.NoError(t, err)

	err = c.load(bytes.NewBufferString(raw))
	require.NoError(t, err)

	home, err := os.UserHomeDir()
	require.NoError(t, err)

	assert.Equal(t, "false", c.variables["core.filemode"])
	assert.Equal(t, "origin", c.variables["branch.devel.remote"])

	// Verify that ~/ expands to the user's home directory.
	assert.Equal(t, filepath.Join(home, "foo.inc"), c.variables["include.path"])
}

func TestCoreExcludesFile(t *testing.T) {
	config, err := globalGitConfig()
	require.NoError(t, err)
	path, err := config.coreExcludesFile()
	require.NoError(t, err)
	t.Log(path)
}

type testCoreExcludesHelper struct {
	*testing.T

	home          string
	xdgConfigHome string
}

func (h *testCoreExcludesHelper) initialize(t *testing.T) {
	h.T = t

	// Create temporary $HOME directory.
	h.home = t.TempDir()
	t.Setenv("HOME", h.home)
	t.Setenv("USERPROFILE", h.home)

	// Create temporary $XDG_CONFIG_HOME directory.
	h.xdgConfigHome = t.TempDir()
	t.Setenv("XDG_CONFIG_HOME", h.xdgConfigHome)

	xdgConfigHomeGit := filepath.Join(h.xdgConfigHome, "git")
	err := os.MkdirAll(xdgConfigHomeGit, 0755)
	require.NoError(t, err)
}

func (h *testCoreExcludesHelper) coreExcludesFile() (string, error) {
	config, err := globalGitConfig()
	require.NoError(h.T, err)
	return config.coreExcludesFile()
}

func (h *testCoreExcludesHelper) writeConfig(path, contents string) {
	err := os.WriteFile(path, []byte(contents), 0644)
	require.NoError(h, err)
}

func TestCoreExcludesFileDefaultWithXdgConfigHome(t *testing.T) {
	h := &testCoreExcludesHelper{}
	h.initialize(t)

	path, err := h.coreExcludesFile()
	require.NoError(t, err)
	assert.Equal(t, filepath.Join(h.xdgConfigHome, "git/ignore"), path)
}

func TestCoreExcludesFileDefaultWithoutXdgConfigHome(t *testing.T) {
	h := &testCoreExcludesHelper{}
	h.initialize(t)
	h.Setenv("XDG_CONFIG_HOME", "")

	path, err := h.coreExcludesFile()
	require.NoError(t, err)
	assert.Equal(t, filepath.Join(h.home, ".config/git/ignore"), path)
}

func TestCoreExcludesFileSetInXdgConfigHomeGitConfig(t *testing.T) {
	h := &testCoreExcludesHelper{}
	h.initialize(t)
	h.writeConfig(filepath.Join(h.xdgConfigHome, "git/config"), `
[core]
excludesFile = ~/foo
	`)

	path, err := h.coreExcludesFile()
	require.NoError(t, err)
	assert.Equal(t, filepath.Join(h.home, "foo"), path)
}

func TestCoreExcludesFileSetInHomeGitConfig(t *testing.T) {
	h := &testCoreExcludesHelper{}
	h.initialize(t)
	h.writeConfig(filepath.Join(h.home, ".gitconfig"), `
[core]
excludesFile = ~/foo
	`)

	path, err := h.coreExcludesFile()
	require.NoError(t, err)
	assert.Equal(t, filepath.Join(h.home, "foo"), path)
}

func TestCoreExcludesFileSetInBoth(t *testing.T) {
	h := &testCoreExcludesHelper{}
	h.initialize(t)
	h.writeConfig(filepath.Join(h.xdgConfigHome, ".gitconfig"), `
[core]
excludesFile = ~/foo1
	`)
	h.writeConfig(filepath.Join(h.home, ".gitconfig"), `
[core]
excludesFile = ~/foo2
	`)

	path, err := h.coreExcludesFile()
	require.NoError(t, err)
	assert.Equal(t, filepath.Join(h.home, "foo2"), path)
}
