package sync

import (
	"os"
	"path/filepath"

	"git.csclub.uwaterloo.ca/public/merlin/config"
)

// jobs to complete before repo sync command should be run
// when done is false: status can be ignored and sync command should continue
// when done is true: sync should exit with status
func preRepoSync(repo *config.Repo) (status int, done bool) {
	status = config.SUCCESS
	done = false

	// clear the rsync log file
	if repo.RsyncLogFile != "" {
		err := os.Truncate(repo.RsyncLogFile, 0)
		if err != nil {
			status = config.FAILURE
			repo.Logger.Error("Error while trying to clear logfile: " + repo.RepoLogFile)
		}
	}

	if repo.TraceHost != "" {
		if status, done = checkIfRepoSyncNeeded(repo); done {

			// run alternative jobs if a full sync will not be done
			if exit := noRepoSyncNeeded(repo); exit != config.SUCCESS {
				status = exit
			}
			return
		}
	}

	return
}

// check if the repo should be synced by comparing their trace file with our own
// returns the exit status of the command used to retrieve the repo's trace file
// returns true for done if and only if no errors occur and it is confirmed that
// 		the trace files are the same or that a termination signal was given
// 		(csc-sync-debian will always sync so done in its case will always be false)
func checkIfRepoSyncNeeded(repo *config.Repo) (status int, done bool) {
	status = config.FAILURE
	done = false

	if repo.DryRun {
		repo.Logger.Debug("dry running so assuming that trace file was changed")
		return config.SUCCESS, false
	}

	repo.Logger.Debug("Retrieving the repo's trace file")

	temp, err := os.CreateTemp("/tmp", "merlin-"+repo.Name+"-trace-*")
	if err != nil {
		repo.Logger.Error(err.Error())
		return
	}
	temp.Close()
	defer os.Remove(temp.Name())

	// get the trace command to copy the last update file
	var cmd []string
	switch repo.SyncType {
	case "csc-sync-archlinux":
		cmd = cscTraceArchLinux(repo, temp.Name())
	case "csc-sync-debian":
		cmd = cscTraceDebian(repo, temp.Name())
	default:
		repo.Logger.Error("Trace files are not implemented for sync type '" + repo.SyncType + "'")
		return
	}

	status = spawnProcessAndWait(repo, cmd)
	if status != config.SUCCESS {
		if status == config.TERMINATED {
			done = true
		}
		return
	}

	repo.Logger.Debug("Comparing the repo's trace file with our own")

	// diff returns true if files are the same (if files are the same then sync is done)
	switch repo.SyncType {
	case "csc-sync-archlinux":
		done = cscTraceArchLinuxDiff(repo, temp.Name())
	case "csc-sync-debian":
		done = cscTraceDebianDiff(repo, temp.Name())
	}

	if done {
		repo.Logger.Info("trace file for " + repo.RsyncHost + " unchanged")

		// debian syncs even when trace file is unchanged
		if repo.SyncType == "csc-sync-debian" {
			done = false
		}
		return
	}

	repo.Logger.Debug("trace file changes found; will attempt to sync")
	return
}

// jobs to do when a full sync will not be run
func noRepoSyncNeeded(repo *config.Repo) (status int) {
	status = config.SUCCESS

	var args []string
	switch repo.SyncType {
	case "csc-sync-archlinux":
		args = cscTraceArchLinuxUpdate(repo)
	}

	status = spawnProcessAndWait(repo, args)
	return
}

func cscTraceArchLinux(repo *config.Repo, newTime string) []string {
	args := []string{
		"curl",
		"--interface", config.Conf.IPv4Address,
		"-s", repo.TraceHost,
		"-o", newTime,
	}

	return args
}

func cscTraceDebian(repo *config.Repo, newTime string) []string {
	args := []string{
		"nice", "rsync", "-tv", "--quiet",
		"-4", "--address=" + config.Conf.IPv4Address,
		repo.RsyncHost + "::" + filepath.Join(repo.RsyncDir, "project/trace", repo.TraceHost),
		newTime,
	}

	return args
}

func cscTraceArchLinuxDiff(repo *config.Repo, newTime string) bool {
	return diffFileContent(repo, newTime, filepath.Join(buildDownloadDir(repo), "lastupdate"))
}

func cscTraceDebianDiff(repo *config.Repo, newTime string) bool {
	return diffFileTime(repo, newTime, filepath.Join(buildDownloadDir(repo), "project/trace", repo.TraceHost))
}

func cscTraceArchLinuxUpdate(repo *config.Repo) (args []string) {
	rsyncDir := repo.RsyncDir
	localDir := repo.LocalDir

	repo.RsyncDir = repo.RsyncDir + "/lastsync"
	repo.LocalDir = repo.LocalDir + "/lastsync"

	args = cscSyncArchLinux(repo)

	repo.RsyncDir = rsyncDir
	repo.LocalDir = localDir

	return args
}
