//go:build !gitgo
// +build !gitgo

package sync

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path"
	"strings"

	"srs.epita.fr/fic-server/libfic"
)

// GitImporter implements an Importer, where files to imports are located
// inside a local directory from your filesystem, backed by git (binary).
type GitImporter struct {
	li     LocalImporter
	Remote string
}

func NewGitImporter(li LocalImporter, remote string) GitImporter {
	return GitImporter{
		li:     li,
		Remote: remote,
	}
}

func (i GitImporter) Id() *string {
	cmdshow := exec.Command("git", "-C", i.li.Base, "show")
	var outshow bytes.Buffer
	cmdshow.Stdout = &outshow
	err := cmdshow.Run()

	var commit string
	if err != nil {
		commit = fmt.Sprintf("error (%s)", err.Error())
	} else {
		commit, err = outshow.ReadString('\n')
		if err == nil {
			commit = strings.TrimPrefix(commit, "commit ")
		} else {
			commit = fmt.Sprintf("error (%s)", err.Error())
		}
	}

	return &commit
}

func (i GitImporter) Init() error {
	// Check if the directory exists, create it if needed
	if err := i.li.Init(); err != nil {
		return err
	}

	// If the directory is empty, clone it
	if n, err := countFileInDir(i.li.Base); err != nil {
		return err
	} else if n == 0 {
		log.Println("Please wait while creating the local git repository...")
		cmdclone := exec.Command("git", "clone", "--recursive", "--depth", "1", "--shallow-submodules", i.Remote, i.li.Base)
		stdout, err := cmdclone.CombinedOutput()
		if err != nil {
			return fmt.Errorf("%w:\n%s", err, stdout)
		}
		log.Println("Local git repository successfully cloned")
	} else if _, err := os.Stat(path.Join(i.li.Base, ".git")); errors.Is(err, os.ErrNotExist) {
		log.Fatal(i.li.Base, "is not a valid git repository and it cannot be initialized because it's not empty.")
	}

	// Check if the .git directory exists, change the origin remote to our
	cmdremote := exec.Command("git", "-C", i.li.Base, "remote", "set-url", "origin", i.Remote)
	stdout, err := cmdremote.CombinedOutput()
	if err != nil {
		return fmt.Errorf("%w:\n%s", err, stdout)
	}

	return nil
}

func (i GitImporter) Sync() error {
	log.Println("Synchronizing local git repository...")
	cmdfetch := exec.Command("git", "-C", i.li.Base, "fetch", "origin")
	stdout, err := cmdfetch.CombinedOutput()
	if err != nil {
		log.Printf("Git repository fetch failed: %s\n%s", err, stdout)
		return fmt.Errorf("%w:\n%s", err, stdout)
	}

	cmdclean := exec.Command("git", "-C", i.li.Base, "clean", "-xfde", "*_MERGED")
	stdout, err = cmdclean.CombinedOutput()
	if err != nil {
		log.Printf("Local git repository clean failed: %s\n%s", err, stdout)
		return fmt.Errorf("%w:\n%s", err, stdout)
	}

	if _, err := os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
		// We have submodules, clean it
		cmdsubclean := exec.Command("git", "-C", i.li.Base, "submodule", "foreach", "--recursive", "git", "clean", "-xfde", "*_MERGED")
		stdout, err = cmdsubclean.CombinedOutput()
		if err != nil {
			log.Printf("Local git repository submodules clean failed: %s\n%s", err, stdout)
			return fmt.Errorf("%w:\n%s", err, stdout)
		}
	}

	cmdreset := exec.Command("git", "-C", i.li.Base, "reset", "--hard", "--recurse-submodule", "origin/master")
	stdout, err = cmdreset.CombinedOutput()
	if err != nil {
		log.Printf("Local git repository reset failed: %s\n%s", err, stdout)
		return fmt.Errorf("%w:\n%s", err, stdout)
	}

	if _, err := os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
		// Treat submodules
		cmdsublfs := exec.Command("git", "-C", i.li.Base, "submodule", "foreach", "--recursive", "git", "lfs", "pull")
		stdout, err = cmdsublfs.CombinedOutput()
		if err != nil {
			log.Printf("Local LFS synchronization failed: %s\n%s", err, stdout)
			return fmt.Errorf("%w:\n%s", err, stdout)
		}
	} else {
		cmdlfs := exec.Command("git", "-C", i.li.Base, "lfs", "pull")
		stdout, err = cmdlfs.CombinedOutput()
		if err != nil {
			log.Printf("Local LFS synchronization failed: %s\n%s", err, stdout)
			return fmt.Errorf("%w:\n%s", err, stdout)
		}
	}

	log.Println("Local git repository synchronized successfully")
	return nil
}

func (i GitImporter) GetThemeLink(th *fic.Theme) (u *url.URL, err error) {
	prefix := ""

	if _, err = os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
		thdir := path.Join(i.li.Base, th.Path)
		cmdremote := exec.Command("git", "-C", thdir, "remote", "get-url", "origin")
		var stdout []byte
		stdout, err = cmdremote.CombinedOutput()
		if err != nil {
			return
		}

		u, err = getForgeBaseLink(string(bytes.TrimSpace(stdout)))

		// Search .git directory
		for {
			if _, err = os.Stat(path.Join(thdir, ".git")); !errors.Is(err, os.ErrNotExist) {
				break
			}

			thdir, _ = path.Split(thdir)
		}
		prefix = strings.TrimPrefix(thdir, i.li.Base)
	} else {
		u, err = getForgeBaseLink(i.Remote)
	}

	if err != nil {
		return
	}

	u.Path = path.Join(u.Path, "-", "tree", "master", strings.TrimPrefix(th.Path, prefix))

	return
}

func (i GitImporter) GetExerciceLink(e *fic.Exercice) (u *url.URL, err error) {
	prefix := ""

	if _, err = os.Stat(path.Join(i.li.Base, ".gitmodules")); !errors.Is(err, os.ErrNotExist) {
		exdir := path.Join(i.li.Base, e.Path)
		cmdremote := exec.Command("git", "-C", exdir, "remote", "get-url", "origin")
		var stdout []byte
		stdout, err = cmdremote.CombinedOutput()
		if err != nil {
			return
		}

		u, err = getForgeBaseLink(string(bytes.TrimSpace(stdout)))

		// Search .git directory
		for {
			if _, err = os.Stat(path.Join(exdir, ".git")); !errors.Is(err, os.ErrNotExist) {
				break
			}

			exdir, _ = path.Split(exdir)
		}
		prefix = strings.TrimPrefix(exdir, i.li.Base)
	} else {
		u, err = getForgeBaseLink(i.Remote)
	}

	if err != nil {
		return
	}

	u.Path = path.Join(u.Path, "-", "tree", "master", strings.TrimPrefix(e.Path, prefix))

	return
}
