package main

import (
	"errors"
	"log"
	"os"

	"github.com/go-git/go-git/v6"
	. "github.com/go-git/go-git/v6/_examples"
	"github.com/go-git/go-git/v6/config"
	"github.com/go-git/go-git/v6/plumbing/client"
	"github.com/go-git/go-git/v6/plumbing/object"
	"github.com/go-git/go-git/v6/plumbing/transport/ssh"
)

// Example of how create a tag and push it to a remote.
func main() {
	CheckArgs("<ssh-url>", "<directory>", "<tag>", "<name>", "<email>", "<public-key>")
	url := os.Args[1]
	directory := os.Args[2]
	tag := os.Args[3]
	key := os.Args[6]

	r, err := cloneRepo(url, directory, key)
	if err != nil {
		log.Printf("clone repo error: %s", err)
		return
	}
	defer func() { _ = r.Close() }()

	created, err := setTag(r, tag)
	if err != nil {
		log.Printf("create tag error: %s", err)
		return
	}

	if created {
		err = pushTags(r, key)
		if err != nil {
			log.Printf("push tag error: %s", err)
			return
		}
	}
}

func cloneRepo(url, dir, publicKeyPath string) (*git.Repository, error) {
	log.Printf("cloning %s into %s", url, dir)
	auth, keyErr := publicKey(publicKeyPath)
	if keyErr != nil {
		return nil, keyErr
	}

	Info("git clone %s", url)
	r, err := git.PlainClone(dir, &git.CloneOptions{
		Progress:      os.Stdout,
		URL:           url,
		ClientOptions: []client.Option{client.WithSSHAuth(auth)},
	})
	if err != nil {
		log.Printf("clone git repo error: %s", err)
		return nil, err
	}

	return r, nil
}

func publicKey(filePath string) (*ssh.PublicKeys, error) {
	var publicKey *ssh.PublicKeys
	sshKey, _ := os.ReadFile(filePath)
	publicKey, err := ssh.NewPublicKeys("git", []byte(sshKey), "")
	if err != nil {
		return nil, err
	}
	return publicKey, err
}

func tagExists(tag string, r *git.Repository) bool {
	tagFoundErr := "tag was found"
	Info("git show-ref --tag")
	tags, err := r.TagObjects()
	if err != nil {
		log.Printf("get tags error: %s", err)
		return false
	}
	res := false
	err = tags.ForEach(func(t *object.Tag) error {
		if t.Name == tag {
			res = true
			return errors.New(tagFoundErr)
		}
		return nil
	})
	if err != nil && err.Error() != tagFoundErr {
		log.Printf("iterate tags error: %s", err)
		return false
	}
	return res
}

func setTag(r *git.Repository, tag string) (bool, error) {
	if tagExists(tag, r) {
		log.Printf("tag %s already exists", tag)
		return false, nil
	}
	log.Printf("Set tag %s", tag)
	h, err := r.Head()
	if err != nil {
		log.Printf("get HEAD error: %s", err)
		return false, err
	}
	Info("git tag -a %s %s -m \"%s\"", tag, h.Hash(), tag)
	_, err = r.CreateTag(tag, h.Hash(), &git.CreateTagOptions{
		Message: tag,
	})
	if err != nil {
		log.Printf("create tag error: %s", err)
		return false, err
	}

	return true, nil
}

func pushTags(r *git.Repository, publicKeyPath string) error {
	auth, _ := publicKey(publicKeyPath)

	po := &git.PushOptions{
		RemoteName:    "origin",
		Progress:      os.Stdout,
		RefSpecs:      []config.RefSpec{config.RefSpec("refs/tags/*:refs/tags/*")},
		ClientOptions: []client.Option{client.WithSSHAuth(auth)},
	}
	Info("git push --tags")
	err := r.Push(po)
	if err != nil {
		if errors.Is(err, git.NoErrAlreadyUpToDate) {
			log.Print("origin remote was up to date, no push done")
			return nil
		}
		log.Printf("push to remote origin error: %s", err)
		return err
	}

	return nil
}
