package git

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/config"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/sirupsen/logrus"
)

// Clone runs a git clone which accounts for different default branches.
func Clone(dir, url string) error {
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		logrus.Debugf("'%s' does not exist, attempting to git clone from '%s'", dir, url)
		_, err := git.PlainClone(dir, false, &git.CloneOptions{URL: url, Tags: git.AllTags})
		if err != nil {
			logrus.Debugf("cloning '%s' default branch failed, attempting from main branch", url)
			_, err := git.PlainClone(dir, false, &git.CloneOptions{
				URL:           url,
				Tags:          git.AllTags,
				ReferenceName: plumbing.ReferenceName("refs/heads/main"),
			})
			if err != nil {
				if strings.Contains(err.Error(), "authentication required") {
					name := filepath.Base(dir)
					return fmt.Errorf("unable to clone %s, does %s exist?", name, url)
				}
				return err
			}
		}
		logrus.Debugf("'%s' has been git cloned successfully", dir)
	} else {
		logrus.Debugf("'%s' already exists, doing nothing", dir)
	}

	return nil
}

// EnsureUpToDate ensures that a git repo on disk has the latest changes (git-fetch).
func EnsureUpToDate(dir string) error {
	repo, err := git.PlainOpen(dir)
	if err != nil {
		return err
	}

	recipeName := filepath.Base(dir)
	isClean, err := IsClean(recipeName)
	if err != nil {
		return err
	}

	if !isClean {
		return fmt.Errorf("'%s' has locally unstaged changes", recipeName)
	}

	branch := "master"
	if _, err := repo.Branch("master"); err != nil {
		if _, err := repo.Branch("main"); err != nil {
			logrus.Debugf("failed to select branch in '%s'", dir)
			return err
		}
		branch = "main"
	}

	logrus.Debugf("choosing '%s' as main git branch in '%s'", branch, dir)

	worktree, err := repo.Worktree()
	if err != nil {
		return err
	}

	refName := fmt.Sprintf("refs/heads/%s", branch)
	checkOutOpts := &git.CheckoutOptions{
		Create: false,
		Force:  true,
		Branch: plumbing.ReferenceName(refName),
	}
	if err := worktree.Checkout(checkOutOpts); err != nil {
		logrus.Debugf("failed to check out '%s' in '%s'", refName, dir)
		return err
	}

	logrus.Debugf("successfully checked out '%s' in '%s'", branch, dir)

	remote, err := repo.Remote("origin")
	if err != nil {
		return err
	}

	fetchOpts := &git.FetchOptions{
		RemoteName: "origin",
		RefSpecs:   []config.RefSpec{"refs/heads/*:refs/remotes/origin/*"},
		Force:      true,
	}
	if err := remote.Fetch(fetchOpts); err != nil {
		if !strings.Contains(err.Error(), "already up-to-date") {
			return err
		}
	}

	logrus.Debugf("successfully fetched all changes in '%s'", dir)

	return nil
}