package main

import (
	"bufio"
	"bytes"
	"fmt"
	"hash/crc32"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/PumpkinSeed/cage"
	"github.com/gohugoio/hugo/commands"
	"github.com/spf13/viper"
)

var (
	version = "21.02.01"
)

func check(e error) {
	if e != nil {
		fmt.Printf("\nGit hook version: %s\n~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~\n", version)
		log.Fatal(e)
		panic(e)
	}
}

func isPublished(gitRepoPath, gitIndexPath, prevcommit, lastcommit string) bool {
	var sout bytes.Buffer
	//var serr bytes.Buffer
	commits := exec.Command("git", "-C", gitRepoPath, "rev-list", fmt.Sprintf("%s..%s", prevcommit, lastcommit))
	commits.Env = os.Environ()
	commits.Env = append(commits.Env, fmt.Sprintf("GIT_INDEX_FILE=%s%s", gitRepoPath, gitIndexPath))
	commits.Stdout = &sout
	//commits.Stderr = &serr
	err := commits.Run()
	check(err)
	for _, commit := range strings.Fields(sout.String()) {
		committedFiles, err := exec.Command("git", "-C", gitRepoPath, "diff-tree", "--no-commit-id", "--name-only", commit).Output()
		check(err)
		for _, commitFile := range strings.Fields(string(committedFiles)) {
			if commitFile == "PUBLISH.trigger.md" {
				return true
			}
		}

		commitMessages, err := exec.Command("git", "-C", gitRepoPath, "show", "-s", "--format=%B", commit).Output()
		check(err)

		if strings.Contains(string(commitMessages), "!publish!") {
			return true
		}
	}
	return false
}

func hugoLogs(logLines string, lines []string) string {
	logLines = logLines + "~~~~~~~~~~ Hugo's logs ~~~~~~~~~~\n"
	for _, d := range lines {
		logLines = logLines + d + "\n"

	}
	logLines = logLines + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
	return logLines
}

func getOutdirAndConfigCatalog(tmpRepoPath, gitRepoPath string) (string, string, string) {
	viper.SetConfigName("config")
	viper.SetConfigType("toml")
	viper.AddConfigPath(filepath.Join(tmpRepoPath))
	err := viper.ReadInConfig()
	check(err)
	//outdirBasePath := viper.GetString("params.sandpointsOutputDirectory")
	outdirChecksum := strconv.FormatUint(uint64(crc32.ChecksumIEEE([]byte(gitRepoPath))), 16)
	reg, err := regexp.Compile("[^a-zA-Z0-9]+")
	check(err)
	outdirRepoName := reg.ReplaceAllString(viper.GetString("title"), "")
	return (strings.ToLower(outdirRepoName) + "-" + outdirChecksum), viper.GetString("params.sandpointsCatalogName"), viper.GetString("params.sandpointsCatalogPrefix")
}

func main() {

	defer os.Exit(0)

	// outDir is where the output of hugo will end up
	// make sure the last part of the path has the right permissions
	// os.TempDir() as first argument in filepath.Join is probably good choice for testing
	outDir := filepath.Join("/", "var", "www", "html", "sandpoints")

	startTime := time.Now()
	logLines := startTime.Format(time.RFC822) + "\n"

	ex, err := os.Executable()
	check(err)

	var gitRepoPath, prevCommit, lastCommit, branchName, gitIndexPath string

	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	if scanner.Text() == "" {
		// if it has no output it comes as post-commit hook
		gitRepoPath = strings.Replace(filepath.Dir(ex), filepath.Join("/", ".git", "hooks"), "", 1)
		//gitRepoPath = "/home/m/gitea-repositories/sandpoints/dev.git"
		gitLastCommit, err := exec.Command("git", "-C", gitRepoPath, "rev-parse", "HEAD").Output()
		check(err)
		gitPrevCommit, err := exec.Command("git", "-C", gitRepoPath, "rev-parse", "HEAD^").Output()
		check(err)
		gitBranchName, err := exec.Command("git", "-C", gitRepoPath, "branch", "-a", "--contains", "HEAD").Output()
		check(err)
		branchName = strings.Replace(strings.TrimSpace(string(gitBranchName)), "* ", "", 1)
		prevCommit, lastCommit, gitIndexPath = strings.TrimSpace(string(gitPrevCommit)), strings.TrimSpace(string(gitLastCommit)), filepath.Join("/", ".git", "index")
	} else {
		// if it has output it comes as post-receive.d/sphook from gitea repo
		revs := strings.Fields(scanner.Text())
		gitRepoPath = strings.Replace(filepath.Dir(ex), filepath.Join("/", "hooks", "post-receive.d"), "", 1)
		prevCommit, lastCommit, branchName, gitIndexPath = revs[0], revs[1], strings.Split(revs[2], "/")[2], filepath.Join("/", "index")
	}

	// there's third one here, revs[2], usually saying refs/heads/master
	// branchName := fmt.Sprintf(strings.Split(revs[2])[2])

	logLines = logLines + "\n" + fmt.Sprintf("Repo: %s\n", gitRepoPath)
	logLines = logLines + fmt.Sprintf("Repo's branch: %s\n", branchName)

	rand.Seed(time.Now().UnixNano())
	worktreeName := fmt.Sprintf("sandpoints_repo_%06d", rand.Intn(100001))
	tmpRepoPath := filepath.Join(os.TempDir(), worktreeName)
	logLines = logLines + fmt.Sprintf("cloned into temporary directory: %s\n", tmpRepoPath)

	if _, err := os.Stat(tmpRepoPath); err == nil {
		err := os.RemoveAll(tmpRepoPath)
		check(err)
	}

	err = os.MkdirAll(tmpRepoPath, 0755)
	check(err)

	//var sout bytes.Buffer
	//var serr bytes.Buffer
	gitAddWorktree := exec.Command("git", "-C", gitRepoPath, "worktree", "add", "--detach", tmpRepoPath)
	gitRemoveWorktree := exec.Command("git", "-C", gitRepoPath, "worktree", "remove", worktreeName)
	gitAddWorktree.Env = os.Environ()
	gitAddWorktree.Env = append(gitAddWorktree.Env, fmt.Sprintf("GIT_INDEX_FILE=%s%s", gitRepoPath, gitIndexPath))
	//gitAddWorktree.Stdout = &sout
	//gitAddWorktree.Stderr = &serr
	err = gitAddWorktree.Run()
	check(err)

	gitRemoveWorktree.Env = os.Environ()
	gitRemoveWorktree.Env = append(gitRemoveWorktree.Env, fmt.Sprintf("GIT_INDEX_FILE=%s%s", gitRepoPath, gitIndexPath))
	defer gitRemoveWorktree.Run()

	outSandpointsDir, sandpointsCatalogName, sandpointsCatalogPrefix := getOutdirAndConfigCatalog(tmpRepoPath, gitRepoPath)
	logLines = logLines + fmt.Sprintf("to be moved to: %s\n", filepath.Join(outDir, outSandpointsDir))
	tmpHugoGiteaPath := filepath.Join(os.TempDir(), fmt.Sprintf("sandpoints_hugo_%06d", rand.Intn(100001)))

	err = os.MkdirAll(filepath.Join(outDir, outSandpointsDir), 0755)
	check(err)

	err = os.RemoveAll(tmpHugoGiteaPath)
	check(err)

	published := isPublished(gitRepoPath, gitIndexPath, prevCommit, lastCommit)

	defer func() {
		if published {
			lastGiteaCommitLog, err := os.Create(filepath.Join(outDir, outSandpointsDir, "last-commit-log.txt"))
			check(err)
			defer lastGiteaCommitLog.Close()
			fmt.Fprintln(lastGiteaCommitLog, logLines)
		} else {
			lastPreviewCommitLog, err := os.Create(filepath.Join(outDir, outSandpointsDir, "_preview", "last-commit-log.txt"))
			check(err)
			defer lastPreviewCommitLog.Close()
			fmt.Fprintln(lastPreviewCommitLog, logLines)
		}
		log.Printf(logLines)
	}()

	hugoGiteaLogs := cage.Start()
	respGitea := commands.Execute([]string{"-s", tmpRepoPath, "-d", tmpHugoGiteaPath, "-e", "gitea", "--templateMetrics"})
	cage.Stop(hugoGiteaLogs)

	if respGitea.Err != nil {
		logLines = fmt.Sprintf("Hugo's attempt to render the web site ended up with an Error! Check out the last commit to capture possible errors.\n\n%s\n~~~~~~~~~~\n\n%s", respGitea.Err, logLines)
		runtime.Goexit()
	} else {
		logLines = hugoLogs(logLines, hugoGiteaLogs.Data)
	}

	if published {
		if _, err := os.Stat(filepath.Join(outDir, outSandpointsDir)); err == nil {
			err := os.RemoveAll(filepath.Join(outDir, outSandpointsDir))
			check(err)
			err = os.MkdirAll(outDir, 0755)
			check(err)
		}
	} else if _, err := os.Lstat(filepath.Join(outDir, outSandpointsDir, "_preview")); err == nil {
		err := os.RemoveAll(filepath.Join(outDir, outSandpointsDir, "_preview"))
		check(err)
	} else if _, err := os.Stat(filepath.Join(outDir, outSandpointsDir, "_preview")); err == nil {
		err := os.RemoveAll(filepath.Join(outDir, outSandpointsDir, "_preview"))
		check(err)
	}

	if published {
		err = os.Rename(tmpHugoGiteaPath, filepath.Join(outDir, outSandpointsDir))
		check(err)
		err = os.Symlink(filepath.Join(outDir, outSandpointsDir), filepath.Join(outDir, outSandpointsDir, "_preview"))
		check(err)
		if sandpointsCatalogPrefix != "" && sandpointsCatalogName != "" {
			err = os.Symlink(filepath.Join(outDir, "libraries", sandpointsCatalogName), filepath.Join(outDir, outSandpointsDir, sandpointsCatalogPrefix))
			check(err)
		}
	} else {
		err = os.MkdirAll(filepath.Join(outDir, outSandpointsDir), 0755)
		check(err)
		err = os.Rename(tmpHugoGiteaPath, filepath.Join(outDir, outSandpointsDir, "_preview"))
		check(err)
		if sandpointsCatalogPrefix != "" && sandpointsCatalogName != "" {
			err = os.Symlink(filepath.Join(outDir, "libraries", sandpointsCatalogName), filepath.Join(outDir, outSandpointsDir, "_preview", sandpointsCatalogPrefix))
			check(err)
		}
	}

	durationMillseconds := int64(time.Since(startTime) / time.Millisecond)
	logLines = logLines + fmt.Sprintf("Total processing time: %d ms\n\nGit hook version: %s\n~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~\n", durationMillseconds, version)
}
