package git

import (
	"io/ioutil"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"strings"

	"coopcloud.tech/abra/pkg/config"
	"github.com/go-git/go-git/v5"
	gitConfigPkg "github.com/go-git/go-git/v5/config"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/format/gitignore"
	"github.com/sirupsen/logrus"
)

// GetRecipeHead retrieves latest HEAD metadata.
func GetRecipeHead(recipeName string) (*plumbing.Reference, error) {
	recipeDir := path.Join(config.RECIPES_DIR, recipeName)

	repo, err := git.PlainOpen(recipeDir)
	if err != nil {
		return nil, err
	}

	head, err := repo.Head()
	if err != nil {
		return nil, err
	}

	return head, nil
}

// IsClean checks if a repo has unstaged changes
func IsClean(repoPath string) (bool, error) {
	repo, err := git.PlainOpen(repoPath)
	if err != nil {
		return false, err
	}

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

	patterns, err := GetExcludesFiles()
	if err != nil {
		return false, err
	}

	if len(patterns) > 0 {
		worktree.Excludes = append(patterns, worktree.Excludes...)
	}

	status, err := worktree.Status()
	if err != nil {
		return false, err
	}

	if status.String() != "" {
		logrus.Debugf("discovered git status in %s: %s", repoPath, status.String())
	} else {
		logrus.Debugf("discovered clean git status in %s", repoPath)
	}

	return status.IsClean(), nil
}

// GetExcludesFiles reads the exlude files from a global gitignore
func GetExcludesFiles() ([]gitignore.Pattern, error) {
	var err error
	var patterns []gitignore.Pattern

	cfg, err := parseGitConfig()
	if err != nil {
		return patterns, err
	}

	excludesfile := getExcludesFile(cfg)
	patterns, err = parseExcludesFile(excludesfile)
	if err != nil {
		return patterns, err
	}

	return patterns, nil
}

func parseGitConfig() (*gitConfigPkg.Config, error) {
	cfg := gitConfigPkg.NewConfig()

	usr, err := user.Current()
	if err != nil {
		return nil, err
	}

	globalGitConfig := filepath.Join(usr.HomeDir, ".gitconfig")
	if _, err := os.Stat(globalGitConfig); err != nil {
		if os.IsNotExist(err) {
			logrus.Debugf("no %s exists, not reading any global gitignore config", globalGitConfig)
			return cfg, nil
		}
		return cfg, err
	}

	b, err := ioutil.ReadFile(globalGitConfig)
	if err != nil {
		return nil, err
	}

	if err := cfg.Unmarshal(b); err != nil {
		return nil, err
	}

	return cfg, err
}

func getExcludesFile(cfg *gitConfigPkg.Config) string {
	for _, sec := range cfg.Raw.Sections {
		if sec.Name == "core" {
			for _, opt := range sec.Options {
				if opt.Key == "excludesfile" {
					return opt.Value
				}
			}
		}
	}

	return "~/.gitignore"
}

func parseExcludesFile(excludesfile string) ([]gitignore.Pattern, error) {
	var ps []gitignore.Pattern

	excludesfile, err := expandTilde(excludesfile)
	if err != nil {
		return nil, err
	}

	if _, err := os.Stat(excludesfile); err != nil {
		if os.IsNotExist(err) {
			logrus.Debugf("no %s exists, skipping reading gitignore paths", excludesfile)
			return ps, nil
		}
		return ps, err
	}

	data, err := ioutil.ReadFile(excludesfile)
	if err != nil {
		return nil, err
	}

	var pathsRaw []string
	for _, s := range strings.Split(string(data), "\n") {
		if !strings.HasPrefix(s, "#") && len(strings.TrimSpace(s)) > 0 {
			pathsRaw = append(pathsRaw, s)
			ps = append(ps, gitignore.ParsePattern(s, nil))
		}
	}

	logrus.Debugf("read global ignore paths: %s", strings.Join(pathsRaw, " "))

	return ps, nil
}

func expandTilde(path string) (string, error) {
	if !strings.HasPrefix(path, "~") {
		return path, nil
	}

	var paths []string
	u, err := user.Current()
	if err != nil {
		return "", err
	}

	for _, p := range strings.Split(path, string(filepath.Separator)) {
		if p == "~" {
			paths = append(paths, u.HomeDir)
		} else {
			paths = append(paths, p)
		}
	}

	return filepath.Join(paths...), nil
}