// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package repository

import (
	"context"
	"fmt"
	"os"
	"strings"

	"code.forgejo.org/f3/gof3/v3/logger"
	"code.forgejo.org/f3/gof3/v3/util"
)

func GitGetSha(ctx context.Context, log logger.MessageInterface, repo, ref string) string {
	sha := util.Command(ctx, log, "git", "-C", repo, "rev-parse", ref)
	return strings.TrimSuffix(sha, "\n")
}

func disableHooks(ctx context.Context, log logger.MessageInterface, dir string) func() {
	if !util.FileExists(dir) {
		return func() {}
	}
	util.Command(ctx, log, "git", "-C", dir, "config", "core.hooksPath", "/dev/null")
	return func() {
		util.Command(ctx, log, "git", "-C", dir, "config", "--unset", "core.hooksPath")
	}
}

func GitMirrorRef(ctx context.Context, log logger.MessageInterface, originURL, originRef, destinationURL, destinationRef string) {
	if log == nil {
		log = logger.NewLogger()
	}
	if originURL == destinationURL {
		log.Log(1, logger.Trace, "do nothing because origin & destination are the same %s\n", originURL)
		return
	}
	log.Log(1, logger.Trace, "%s:%s => %s:%s\n", originURL, originRef, destinationURL, destinationRef)
	defer disableHooks(ctx, log, destinationURL)()
	if util.FileExists(originURL) {
		util.Command(ctx, log, "git", "-C", originURL, "push", destinationURL, "+"+originRef+":"+destinationRef)
	} else {
		if util.FileExists(destinationURL) {
			util.Command(ctx, log, "git", "-C", destinationURL, "fetch", originURL, "+"+originRef+":"+destinationRef)
		} else {
			directory, err := os.MkdirTemp("", "driverRepository")
			if err != nil {
				panic(err)
			}
			defer func() {
				err := os.RemoveAll(directory)
				if err != nil {
					panic(err)
				}
			}()
			util.Command(ctx, log, "git", "clone", "--bare", "--depth", "1", originURL, directory)
			util.Command(ctx, log, "git", "-C", directory, "fetch", "origin", originRef)
			util.Command(ctx, log, "git", "-C", directory, "push", destinationURL, "+FETCH_HEAD:"+destinationRef)
		}
	}
}

func GitMirror(ctx context.Context, log logger.MessageInterface, origin, destination, internalRef string) {
	if log == nil {
		log = logger.NewLogger()
	}
	if origin == destination {
		log.Log(1, logger.Trace, "do nothing because origin & destination are the same %s\n", origin)
		return
	}
	log.Log(1, logger.Trace, "%s => %s\n", origin, destination)
	defer disableHooks(ctx, log, destination)()
	var excludeInternalRef []string
	if internalRef != "" {
		excludeInternalRef = []string{fmt.Sprintf("^%s", internalRef)}
	}
	if util.FileExists(origin) {
		args := append([]string{"-C", origin, "push", destination, "+refs/*:refs/*"}, excludeInternalRef...)
		util.Command(ctx, log, "git", args...)
		if internalRef != "" {
			util.Command(ctx, log, "git", "-C", origin, "push", destination, fmt.Sprintf("+%s:refs/f3/*", internalRef))
		}
	} else {
		if util.FileExists(destination) {
			util.Command(ctx, log, "git", "-C", destination, "remote", "add", "--mirror=fetch", "fetchMirror", origin)
			defer func() { util.Command(ctx, log, "git", "-C", destination, "remote", "remove", "fetchMirror") }()
			util.Command(ctx, log, "git", "-C", destination, "fetch", "fetchMirror")
		} else {
			directory, err := os.MkdirTemp("", "driverRepository")
			if err != nil {
				panic(err)
			}
			defer func() {
				err := os.RemoveAll(directory)
				if err != nil {
					panic(err)
				}
			}()
			util.Command(ctx, log, "git", "clone", "--mirror", origin, directory)
			args := append([]string{"-C", directory, "push", destination, "+refs/*:refs/*"}, excludeInternalRef...)
			util.Command(ctx, log, "git", args...)
			if internalRef != "" {
				util.Command(ctx, log, "git", "-C", directory, "push", destination, fmt.Sprintf("+%s:refs/f3/*", internalRef))
			}
		}
	}
}
