package giq

import (
	"fmt"
	"io"
	"math/rand"
	"os"
	"path"
	"path/filepath"
	"strings"

	gogit "github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/object"
)

func (g *Gogit) Whoami() string {
	return "I am Gogit!"
}

func detectGitPath(path string) (string, error) {
	path, err := filepath.Abs(path)
	if err != nil {
		return "", err
	}

	for {
		fi, err := os.Stat(filepath.Join(path, ".git"))
		if err == nil {
			if !fi.IsDir() {
				return "", fmt.Errorf(".git exist but is not a directory")
			}
			return filepath.Join(path, ".git"), nil
		}
		if !os.IsNotExist(err) {
			return "", err
		}

		ok, err := isGitDir(path)
		if err != nil {
			return "", err
		}
		if ok {
			return path, nil
		}

		if parent := filepath.Dir(path); parent == path {
			return "", fmt.Errorf(".git not found")
		} else {
			path = parent
		}
	}
}

func isGitDir(path string) (bool, error) {
	markers := []string{"HEAD", "objects", "refs"}

	for _, marker := range markers {
		_, err := os.Stat(filepath.Join(path, marker))
		if err == nil {
			continue
		}
		if !os.IsNotExist(err) {
			return false, err
		} else {
			return false, nil
		}
	}

	return true, nil
}

func isPublish(r gogit.Repository, prevCommit, lastCommit string) bool {
	ret := false
	pCommit, err := r.ResolveRevision(plumbing.Revision(prevCommit))
	check(err)
	lCommit, err := r.ResolveRevision(plumbing.Revision(lastCommit))
	check(err)
	// fileName := "README.md"
	hashCommitRange, err := r.Log(&gogit.LogOptions{
		From: *lCommit,
		// FileName: &fileName,
		// Order:
	})
	check(err)
	hashCommitMap := make(map[string]bool)
	err = hashCommitRange.ForEach(func(c *object.Commit) error {
		// fmt.Println(c.Hash.String(), c.Message)
		hashCommitMap[c.Hash.String()] = true
		ok := strings.Contains(c.Message, "!publish!")
		if ok {
			ret = true
			return io.EOF
		} else if c.Hash == *pCommit {
			// return storer.ErrStop
			return io.EOF
		}
		return nil
	})

	if ret {
		return true
	}

	fileName := "PUBLISH_TRIGGER.md"
	fileNameCommitRange, err := r.Log(&gogit.LogOptions{
		FileName: &fileName,
	})
	check(err)
	err = fileNameCommitRange.ForEach(func(c *object.Commit) error {
		_, ok := hashCommitMap[c.Hash.String()]
		if ok {
			// return storer.ErrStop
			ret = true
			return io.EOF
		}
		return nil
	})

	// fmt.Println(hashCommitMap)
	if ret {
		return true
	}

	return false
}

func (g *Gogit) AddBareWorktree(gitRepoPath, gitIndexPath, gitWorktree, gexe string) {
	tmpRepoPath := filepath.Join(os.TempDir(), gitWorktree)
	_, err := gogit.PlainClone(tmpRepoPath, false, &gogit.CloneOptions{
		URL:   gitRepoPath,
		Depth: 1,
		Tags:  gogit.NoTags,
		// NoCheckout: true,
	})
	check(err)
	// fmt.Println("WORKTREE:", tmpRepoPath)
}
func (g *Gogit) RemoveBareWorktree(gitRepoPath, gitIndexPath, gitWorktree, gexe string) {
	tmpRepoPath := filepath.Join(os.TempDir(), gitWorktree)
	err := os.RemoveAll(tmpRepoPath)
	check(err)
}

func (g *Gogit) GitStruct(hookPath, hookContext, hookStdinput, gexe string) Git {
	var git Git
	_ = gexe

	gitBase, err := detectGitPath(hookPath)
	check(err)

	repoPath := gitBase
	git.IsBare = true

	base := path.Base(gitBase)
	if base == ".git" {
		git.IsBare = false
		repoPath = strings.Replace(gitBase, ".git", "", 1)
	}

	r, err := gogit.PlainOpen(repoPath)
	check(err)

	gitPreviousCommit, err := r.ResolveRevision(plumbing.Revision("HEAD^"))
	check(err)
	headCommit, err := r.Head()
	check(err)

	prevCommit := gitPreviousCommit.String()
	lastCommit := headCommit.Hash().String()

	if hookContext == "PostReceive" {
		revs := strings.Fields(hookStdinput)
		prevCommit = revs[0]
		lastCommit = revs[1]
	}

	git.Publish = isPublish(*r, prevCommit, lastCommit)
	git.RepoPath = repoPath
	git.PreviousCommit = prevCommit
	git.LastCommit = lastCommit
	git.Branch = headCommit.Name().Short()

	git.IndexPath = filepath.Join("/", ".git", "index")
	if git.IsBare {
		git.IndexPath = filepath.Join("/", "index")
	}

	git.Worktree = fmt.Sprintf("sandpoints_repo_%06d", rand.Intn(100001))
	git.TmpRepoPath = filepath.Join(os.TempDir(), git.Worktree)
	return git
}
