package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

var (
	outputStream io.Writer = os.Stdout
	errStream    io.Writer = os.Stderr
)

type Command struct {
	releaseBranch  string
	releaseVariant string
	gitRoot        string
	release        string
	artifactBucket string

	makeTarget string
	makeArgs   []string

	dryRun bool
}

func (c *Command) buildProject(projectPath string) error {
	commandArgs := []string{
		"-C",
		filepath.Join(c.gitRoot, "projects", projectPath),
	}
	allMakeTargets := strings.Split(c.makeTarget, ",")
	commandArgs = append(commandArgs, allMakeTargets...)
	commandArgs = append(commandArgs, c.makeArgs...)

	cmd := exec.Command("make", commandArgs...)
	log.Printf("Executing: %s", strings.Join(cmd.Args, " "))
	cmd.Stdout = outputStream
	cmd.Stderr = errStream
	if !c.dryRun {
		err := cmd.Run()
		if err != nil {
			return fmt.Errorf("Error running make: %v", err)
		}
	}
	return nil
}

func main() {
	target := flag.String("target", "release", "Make target")
	releaseBranch := flag.String("release-branch", "1-19", "Release branch to test")
	releaseVariant := flag.String("release-variant", "", "Release variant to test")
	release := flag.String("release", "1", "Release to test")
	region := flag.String("region", "us-west-2", "AWS region to use")
	accountId := flag.String("account-id", "", "AWS Account ID to use")
	imageRepo := flag.String("image-repo", "", "Container image repository")
	artifactBucket := flag.String("artifact-bucket", "", "S3 bucket for artifacts")
	gitRoot := flag.String("git-root", "", "Git root directory")
	dryRun := flag.Bool("dry-run", false, "Echo out commands, but don't run them")
	rebuildAll := flag.Bool("rebuild-all", false, "Rebuild all projects, regardless of changes present")
	buildKubeFirst := flag.Bool("build-kubernetes-first", true, "Build kubernetes projects first then other components")

	flag.Parse()
	log.Printf("Running postsubmit - dry-run: %t", *dryRun)

	c := &Command{
		releaseBranch:  *releaseBranch,
		releaseVariant: *releaseVariant,
		release:        *release,
		artifactBucket: *artifactBucket,
		gitRoot:        *gitRoot,
		makeTarget:     *target,
		dryRun:         *dryRun,
	}
	if c.gitRoot == "" {
		gitRootOutput, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
		if err != nil {
			log.Fatalf("Error running finding git root: %v", err)
		}
		c.gitRoot = strings.Fields(string(gitRootOutput))[0]
	}
	c.makeArgs = []string{
		fmt.Sprintf("RELEASE_BRANCH=%s", c.releaseBranch),
		fmt.Sprintf("RELEASE=%s", c.release),
		fmt.Sprintf("AWS_REGION=%s", *region),
		fmt.Sprintf("AWS_ACCOUNT_ID=%s", *accountId),
		fmt.Sprintf("IMAGE_REPO=%s", *imageRepo),
	}

	cmd := exec.Command("git", "-C", c.gitRoot, "diff", "--name-only", "HEAD^", "HEAD")
	log.Printf("Executing command: %s", strings.Join(cmd.Args, " "))
	gitDiffOutput, err := cmd.Output()
	if err != nil {
		log.Fatalf("error running git diff: %v\n%s", err, string(gitDiffOutput))
	}
	filesChanged := strings.Fields(string(gitDiffOutput))

	allChanged := false
	buildOrderKube := []string{
		"kubernetes/release",
		"kubernetes/kubernetes",
		"kubernetes/cloud-provider-aws",
	}

	buildOrder := []string{
		"containernetworking/plugins",
		"coredns/coredns",
		"etcd-io/etcd",
		"kubernetes-sigs/aws-iam-authenticator",
		"kubernetes-sigs/metrics-server",
		"kubernetes-csi/external-attacher",
		"kubernetes-csi/external-resizer",
		"kubernetes-csi/livenessprobe",
		"kubernetes-csi/node-driver-registrar",
		"kubernetes-csi/external-snapshotter",
		"kubernetes-csi/external-provisioner",
	}

	if *buildKubeFirst {
		buildOrder = append(buildOrderKube[:], buildOrder...)
	} else {
		buildOrder = append(buildOrder[:], buildOrderKube...)
	}

	type changedStruct struct {
		changed bool
	}
	projects := make(map[string]*changedStruct)
	for _, projectPath := range buildOrder {
		projects[projectPath] = &changedStruct{}
	}

	if *rebuildAll {
		allChanged = true
	} else {
		for _, file := range filesChanged {
			for projectPath := range projects {
				if strings.Contains(file, projectPath) {
					projects[projectPath].changed = true
				}
			}
			r := regexp.MustCompile("^Makefile$|^Common.mk$|cmd/main_postsubmit.go|EKS_DISTRO_.*_TAG_FILE|^release/.*|^build/lib/.*")
			if r.MatchString(file) {
				allChanged = true
			}
		}
	}

	for _, projectPath := range buildOrder {
		if projects[projectPath].changed || allChanged {
			err = c.buildProject(projectPath)
			if err != nil {
				log.Fatalf("error building %s: %v", projectPath, err)
			}
		}
	}
}