package git

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

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/pkg/errors"
	"github.com/portainer/portainer/api/archive"
	"github.com/stretchr/testify/assert"
)

func setup(t *testing.T) string {
	dir := t.TempDir()
	bareRepoDir := filepath.Join(dir, "test-clone.git")

	file, err := os.OpenFile("./testdata/test-clone-git-repo.tar.gz", os.O_RDONLY, 0755)
	if err != nil {
		t.Fatal(errors.Wrap(err, "failed to open an archive"))
	}

	err = archive.ExtractTarGz(file, dir)
	if err != nil {
		t.Fatal(errors.Wrapf(err, "failed to extract file from the archive to a folder %s", dir))
	}

	return bareRepoDir
}

func Test_ClonePublicRepository_Shallow(t *testing.T) {
	service := Service{git: gitClient{preserveGitDirectory: true}} // no need for http client since the test access the repo via file system.
	repositoryURL := setup(t)
	referenceName := "refs/heads/main"

	dir := t.TempDir()
	t.Logf("Cloning into %s", dir)
	err := service.CloneRepository(dir, repositoryURL, referenceName, "", "")
	assert.NoError(t, err)
	assert.Equal(t, 1, getCommitHistoryLength(t, err, dir), "cloned repo has incorrect depth")
}

func Test_ClonePublicRepository_NoGitDirectory(t *testing.T) {
	service := Service{git: gitClient{preserveGitDirectory: false}} // no need for http client since the test access the repo via file system.
	repositoryURL := setup(t)
	referenceName := "refs/heads/main"

	dir := t.TempDir()
	t.Logf("Cloning into %s", dir)
	err := service.CloneRepository(dir, repositoryURL, referenceName, "", "")
	assert.NoError(t, err)
	assert.NoDirExists(t, filepath.Join(dir, ".git"))
}

func Test_cloneRepository(t *testing.T) {
	service := Service{git: gitClient{preserveGitDirectory: true}} // no need for http client since the test access the repo via file system.

	repositoryURL := setup(t)
	referenceName := "refs/heads/main"

	dir := t.TempDir()
	t.Logf("Cloning into %s", dir)

	err := service.cloneRepository(dir, cloneOptions{
		repositoryUrl: repositoryURL,
		referenceName: referenceName,
		depth:         10,
	})

	assert.NoError(t, err)
	assert.Equal(t, 4, getCommitHistoryLength(t, err, dir), "cloned repo has incorrect depth")
}

func Test_latestCommitID(t *testing.T) {
	service := Service{git: gitClient{preserveGitDirectory: true}} // no need for http client since the test access the repo via file system.

	repositoryURL := setup(t)
	referenceName := "refs/heads/main"

	id, err := service.LatestCommitID(repositoryURL, referenceName, "", "")

	assert.NoError(t, err)
	assert.Equal(t, "68dcaa7bd452494043c64252ab90db0f98ecf8d2", id)
}

func getCommitHistoryLength(t *testing.T, err error, dir string) int {
	repo, err := git.PlainOpen(dir)
	if err != nil {
		t.Fatalf("can't open a git repo at %s with error %v", dir, err)
	}
	iter, err := repo.Log(&git.LogOptions{All: true})
	if err != nil {
		t.Fatalf("can't get a commit history iterator with error %v", err)
	}
	count := 0
	err = iter.ForEach(func(_ *object.Commit) error {
		count++
		return nil
	})
	if err != nil {
		t.Fatalf("can't iterate over the commit history with error %v", err)
	}
	return count
}

type testDownloader struct {
	called bool
}

func (t *testDownloader) download(_ context.Context, _ string, _ cloneOptions) error {
	t.called = true
	return nil
}

func (t *testDownloader) latestCommitID(_ context.Context, _ fetchOptions) (string, error) {
	return "", nil
}

func Test_cloneRepository_azure(t *testing.T) {
	tests := []struct {
		name   string
		url    string
		called bool
	}{
		{
			name:   "Azure HTTP URL",
			url:    "https://Organisation@dev.azure.com/Organisation/Project/_git/Repository",
			called: true,
		},
		{
			name:   "Azure SSH URL",
			url:    "git@ssh.dev.azure.com:v3/Organisation/Project/Repository",
			called: true,
		},
		{
			name:   "Something else",
			url:    "https://example.com",
			called: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			azure := &testDownloader{}
			git := &testDownloader{}

			s := &Service{azure: azure, git: git}
			s.cloneRepository("", cloneOptions{repositoryUrl: tt.url, depth: 1})

			// if azure API is called, git isn't and vice versa
			assert.Equal(t, tt.called, azure.called)
			assert.Equal(t, tt.called, !git.called)
		})
	}
}