package git

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/signal"
	"strings"

	"coopcloud.tech/abra/pkg/i18n"
	"coopcloud.tech/abra/pkg/log"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
)

// gitCloneIgnoreErr checks whether we can ignore a git clone error or not.
func gitCloneIgnoreErr(err error) bool {
	if strings.Contains(err.Error(), "authentication required") {
		return true
	}

	if strings.Contains(err.Error(), "remote repository is empty") {
		return true
	}

	return false
}

// Clone runs a git clone which accounts for different default branches. This
// function respects Ctrl+C (SIGINT) calls from the user, cancelling the
// context and deleting the (typically) half-baked clone of the repository.
// This avoids broken state for future clone / recipe ops.
func Clone(dir, url string) error {
	ctx := context.Background()
	ctx, cancelCtx := context.WithCancel(ctx)

	sigIntCh := make(chan os.Signal, 1)
	signal.Notify(sigIntCh, os.Interrupt)
	defer func() {
		signal.Stop(sigIntCh)
		cancelCtx()
	}()

	errCh := make(chan error)

	go func() {
		if _, err := os.Stat(dir); os.IsNotExist(err) {
			log.Debug(i18n.G("git clone: %s", url))

			_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
				URL:           url,
				Tags:          git.AllTags,
				ReferenceName: plumbing.ReferenceName("refs/heads/main"),
				SingleBranch:  true,
			})

			if err != nil && gitCloneIgnoreErr(err) {
				log.Debug(i18n.G("git clone: %s cloned successfully", dir))
				errCh <- nil
			}

			if err := ctx.Err(); err != nil {
				errCh <- errors.New(i18n.G("git clone %s: cancelled due to interrupt", dir))
			}

			if err != nil {
				log.Debug(i18n.G("git clone: main branch failed, attempting master branch"))

				_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
					URL:           url,
					Tags:          git.AllTags,
					ReferenceName: plumbing.ReferenceName("refs/heads/master"),
					SingleBranch:  true,
				})

				if err != nil && gitCloneIgnoreErr(err) {
					log.Debug(i18n.G("git clone: %s cloned successfully", dir))
					errCh <- nil
				}

				if err != nil {
					errCh <- err
				}
			}

			log.Debug(i18n.G("git clone: %s cloned successfully", dir))
		} else {
			log.Debug(i18n.G("git clone: %s already exists", dir))
		}

		errCh <- nil
	}()

	select {
	case <-sigIntCh:
		cancelCtx()
		fmt.Println() // NOTE(d1): newline after ^C
		if err := os.RemoveAll(dir); err != nil {
			return errors.New(i18n.G("unable to clean up git clone of %s: %s", dir, err))
		}
		return errors.New(i18n.G("git clone %s: cancelled due to interrupt", dir))
	case err := <-errCh:
		return err
	}

	return nil
}
