package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/cncd/pipeline/pipeline/frontend"
	"github.com/cncd/pipeline/pipeline/frontend/yaml"
	"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"

	"github.com/urfave/cli"
)

var compileCommand = cli.Command{
	Name:   "compile",
	Usage:  "compile the yaml file",
	Action: compileAction,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "in",
			Value: "pipeline.yml",
		},
		cli.StringFlag{
			Name:  "out",
			Value: "pipeline.json",
		},
		cli.StringSliceFlag{
			Name: "volumes",
		},
		cli.StringSliceFlag{
			Name: "privileged",
			Value: &cli.StringSlice{
				"plugins/docker",
				"plugins/gcr",
				"plugins/ecr",
			},
		},
		cli.StringFlag{
			Name:  "prefix",
			Value: "pipeline",
		},
		cli.BoolFlag{
			Name: "local",
		},
		//
		// volume caching
		//
		cli.BoolFlag{
			Name:   "volume-cache",
			EnvVar: "CI_VOLUME_CACHE",
		},
		cli.StringFlag{
			Name:   "volume-cache-base",
			Value:  "/var/lib/drone",
			EnvVar: "CI_VOLUME_CACHE_BASE",
		},
		//
		// s3 caching
		//
		cli.BoolFlag{
			Name:   "aws-cache",
			EnvVar: "CI_AWS_CACHE",
		},
		cli.StringFlag{
			Name:   "aws-region",
			EnvVar: "AWS_REGION",
		},
		cli.StringFlag{
			Name:   "aws-bucket",
			EnvVar: "AWS_BUCKET",
		},
		cli.StringFlag{
			Name:   "aws-access-key-id",
			EnvVar: "AWS_ACCESS_KEY_ID",
		},
		cli.StringFlag{
			Name:   "aws-secret-access-key",
			EnvVar: "AWS_SECRET_ACCESS_KEY",
		},
		//
		// registry credentials
		//
		cli.StringFlag{
			Name:   "registry-hostname",
			EnvVar: "CI_REGISTRY_HOSTNAME",
		},
		cli.StringFlag{
			Name:   "registry-username",
			EnvVar: "CI_REGISTRY_USERNAME",
		},
		cli.StringFlag{
			Name:   "registry-password",
			EnvVar: "CI_REGISTRY_PASSWORD",
		},
		//
		// workspace default
		//
		cli.StringFlag{
			Name:  "workspace-base",
			Value: "/pipeline",
		},
		cli.StringFlag{
			Name:  "workspace-path",
			Value: "src",
		},
		//
		// netrc parameters
		//
		cli.StringFlag{
			Name:   "netrc-username",
			EnvVar: "CI_NETRC_USERNAME",
		},
		cli.StringFlag{
			Name:   "netrc-password",
			EnvVar: "CI_NETRC_PASSWORD",
		},
		cli.StringFlag{
			Name:   "netrc-machine",
			EnvVar: "CI_NETRC_MACHINE",
		},
		//
		// resource limit parameters
		//
		cli.Int64Flag{
			Name:   "limit-mem-swap",
			EnvVar: "CI_LIMIT_MEM_SWAP",
		},
		cli.Int64Flag{
			Name:   "limit-mem",
			EnvVar: "CI_LIMIT_MEM",
		},
		cli.Int64Flag{
			Name:   "limit-shm-size",
			EnvVar: "CI_LIMIT_SHM_SIZE",
		},
		cli.Int64Flag{
			Name:   "limit-cpu-quota",
			EnvVar: "CI_LIMIT_CPU_QUOTA",
		},
		cli.Int64Flag{
			Name:   "limit-cpu-shares",
			EnvVar: "CI_LIMIT_CPU_SHARES",
		},
		cli.StringFlag{
			Name:   "limit-cpu-set",
			EnvVar: "CI_LIMIT_CPU_SET",
		},
		//
		// metadata parameters
		//
		cli.StringFlag{
			Name:   "system-arch",
			Value:  "linux/amd64",
			EnvVar: "CI_SYSTEM_ARCH",
		},
		cli.StringFlag{
			Name:   "system-name",
			Value:  "pipec",
			EnvVar: "CI_SYSTEM_NAME",
		},
		cli.StringFlag{
			Name:   "system-link",
			Value:  "https://github.com/cncd/pipec",
			EnvVar: "CI_SYSTEM_LINK",
		},
		cli.StringFlag{
			Name:   "repo-name",
			EnvVar: "CI_REPO_NAME",
		},
		cli.StringFlag{
			Name:   "repo-link",
			EnvVar: "CI_REPO_LINK",
		},
		cli.StringFlag{
			Name:   "repo-remote-url",
			EnvVar: "CI_REPO_REMOTE",
		},
		cli.StringFlag{
			Name:   "repo-private",
			EnvVar: "CI_REPO_PRIVATE",
		},
		cli.IntFlag{
			Name:   "build-number",
			EnvVar: "CI_BUILD_NUMBER",
		},
		cli.Int64Flag{
			Name:   "build-created",
			EnvVar: "CI_BUILD_CREATED",
		},
		cli.Int64Flag{
			Name:   "build-started",
			EnvVar: "CI_BUILD_STARTED",
		},
		cli.Int64Flag{
			Name:   "build-finished",
			EnvVar: "CI_BUILD_FINISHED",
		},
		cli.StringFlag{
			Name:   "build-status",
			EnvVar: "CI_BUILD_STATUS",
		},
		cli.StringFlag{
			Name:   "build-event",
			EnvVar: "CI_BUILD_EVENT",
		},
		cli.StringFlag{
			Name:   "build-link",
			EnvVar: "CI_BUILD_LINK",
		},
		cli.StringFlag{
			Name:   "build-target",
			EnvVar: "CI_BUILD_TARGET",
		},
		cli.StringFlag{
			Name:   "commit-sha",
			EnvVar: "CI_COMMIT_SHA",
		},
		cli.StringFlag{
			Name:   "commit-ref",
			EnvVar: "CI_COMMIT_REF",
		},
		cli.StringFlag{
			Name:   "commit-refspec",
			EnvVar: "CI_COMMIT_REFSPEC",
		},
		cli.StringFlag{
			Name:   "commit-branch",
			EnvVar: "CI_COMMIT_BRANCH",
		},
		cli.StringFlag{
			Name:   "commit-message",
			EnvVar: "CI_COMMIT_MESSAGE",
		},
		cli.StringFlag{
			Name:   "commit-author-name",
			EnvVar: "CI_COMMIT_AUTHOR_NAME",
		},
		cli.StringFlag{
			Name:   "commit-author-avatar",
			EnvVar: "CI_COMMIT_AUTHOR_AVATAR",
		},
		cli.StringFlag{
			Name:   "commit-author-email",
			EnvVar: "CI_COMMIT_AUTHOR_EMAIL",
		},
		cli.IntFlag{
			Name:   "prev-build-number",
			EnvVar: "CI_PREV_BUILD_NUMBER",
		},
		cli.Int64Flag{
			Name:   "prev-build-created",
			EnvVar: "CI_PREV_BUILD_CREATED",
		},
		cli.Int64Flag{
			Name:   "prev-build-started",
			EnvVar: "CI_PREV_BUILD_STARTED",
		},
		cli.Int64Flag{
			Name:   "prev-build-finished",
			EnvVar: "CI_PREV_BUILD_FINISHED",
		},
		cli.StringFlag{
			Name:   "prev-build-status",
			EnvVar: "CI_PREV_BUILD_STATUS",
		},
		cli.StringFlag{
			Name:   "prev-build-event",
			EnvVar: "CI_PREV_BUILD_EVENT",
		},
		cli.StringFlag{
			Name:   "prev-build-link",
			EnvVar: "CI_PREV_BUILD_LINK",
		},
		cli.StringFlag{
			Name:   "prev-commit-sha",
			EnvVar: "CI_PREV_COMMIT_SHA",
		},
		cli.StringFlag{
			Name:   "prev-commit-ref",
			EnvVar: "CI_PREV_COMMIT_REF",
		},
		cli.StringFlag{
			Name:   "prev-commit-refspec",
			EnvVar: "CI_PREV_COMMIT_REFSPEC",
		},
		cli.StringFlag{
			Name:   "prev-commit-branch",
			EnvVar: "CI_PREV_COMMIT_BRANCH",
		},
		cli.StringFlag{
			Name:   "prev-commit-message",
			EnvVar: "CI_PREV_COMMIT_MESSAGE",
		},
		cli.StringFlag{
			Name:   "prev-commit-author-name",
			EnvVar: "CI_PREV_COMMIT_AUTHOR_NAME",
		},
		cli.StringFlag{
			Name:   "prev-commit-author-avatar",
			EnvVar: "CI_PREV_COMMIT_AUTHOR_AVATAR",
		},
		cli.StringFlag{
			Name:   "prev-commit-author-email",
			EnvVar: "CI_PREV_COMMIT_AUTHOR_EMAIL",
		},
		cli.IntFlag{
			Name:   "job-number",
			EnvVar: "CI_JOB_NUMBER",
		},
		// cli.StringFlag{
		// 	Name:   "job-matrix",
		// 	EnvVar: "CI_JOB_MATRIX",
		// },
	},
}

func compileAction(c *cli.Context) (err error) {
	file := c.Args().First()
	if file == "" {
		file = c.String("in")
	}

	conf, err := yaml.ParseFile(file)
	if err != nil {
		return err
	}

	// configure volumes for local execution
	volumes := c.StringSlice("volumes")
	if c.Bool("local") {
		var (
			workspaceBase = conf.Workspace.Base
			workspacePath = conf.Workspace.Path
		)
		if workspaceBase == "" {
			workspaceBase = c.String("workspace-base")
		}
		if workspacePath == "" {
			workspacePath = c.String("workspace-path")
		}
		dir, _ := filepath.Abs(filepath.Dir(file))
		volumes = append(volumes, dir+":"+path.Join(workspaceBase, workspacePath))
	}

	// secrets from environment variable
	var secrets []compiler.Secret
	for _, env := range os.Environ() {
		parts := strings.Split(env, "=")
		secrets = append(secrets, compiler.Secret{
			Name:  parts[0],
			Value: parts[1],
		})
	}

	// compiles the yaml file
	compiled := compiler.New(
		compiler.WithResourceLimit(
			c.Int64("limit-mem-swap"),
			c.Int64("limit-mem"),
			c.Int64("limit-shm-size"),
			c.Int64("limit-cpu-quota"),
			c.Int64("limit-cpu-shares"),
			c.String("limit-cpu-set"),
		),
		compiler.WithRegistry(
			compiler.Registry{
				Hostname: c.String("registry-hostname"),
				Username: c.String("registry-username"),
				Password: c.String("registry-password"),
			},
		),
		compiler.WithEscalated(
			c.StringSlice("privileged")...,
		),
		compiler.WithSecret(secrets...),
		compiler.WithVolumes(volumes...),
		compiler.WithWorkspace(
			c.String("workspace-base"),
			c.String("workspace-path"),
		),
		compiler.WithPrefix(
			c.String("prefix"),
		),
		compiler.WithProxy(),
		compiler.WithLocal(
			c.Bool("local"),
		),
		compiler.WithNetrc(
			c.String("netrc-username"),
			c.String("netrc-password"),
			c.String("netrc-machine"),
		),
		compiler.WithMetadata(
			metadataFromContext(c),
		),
		compiler.WithOption(
			compiler.WithVolumeCacher(
				c.String("volume-cache-base"),
			),
			c.Bool("volume-cache"),
		),
		compiler.WithOption(
			compiler.WithS3Cacher(
				c.String("aws-access-key-id"),
				c.String("aws-secret-access-key"),
				c.String("aws-region"),
				c.String("aws-bucket"),
			),
			c.Bool("aws-cache"),
		),
	).Compile(conf)

	// marshal the compiled spec to formatted yaml
	out, err := json.MarshalIndent(compiled, "", "  ")
	if err != nil {
		return err
	}

	// create output file with option to dump to stdout
	var writer = os.Stdout
	output := c.String("out")
	if output != "-" {
		writer, err = os.Create(output)
		if err != nil {
			return err
		}
	}
	defer writer.Close()

	_, err = writer.Write(out)
	if err != nil {
		return err
	}

	if writer != os.Stdout {
		fmt.Fprintf(os.Stdout, "Successfully compiled %s to %s\n", file, output)
	}
	return nil
}

// return the metadata from the cli context.
func metadataFromContext(c *cli.Context) frontend.Metadata {
	return frontend.Metadata{
		Repo: frontend.Repo{
			Name:    c.String("repo-name"),
			Link:    c.String("repo-link"),
			Remote:  c.String("repo-remote-url"),
			Private: c.Bool("repo-private"),
		},
		Curr: frontend.Build{
			Number:   c.Int("build-number"),
			Created:  c.Int64("build-created"),
			Started:  c.Int64("build-started"),
			Finished: c.Int64("build-finished"),
			Status:   c.String("build-status"),
			Event:    c.String("build-event"),
			Link:     c.String("build-link"),
			Target:   c.String("build-target"),
			Commit: frontend.Commit{
				Sha:     c.String("commit-sha"),
				Ref:     c.String("commit-ref"),
				Refspec: c.String("commit-refspec"),
				Branch:  c.String("commit-branch"),
				Message: c.String("commit-message"),
				Author: frontend.Author{
					Name:   c.String("commit-author-name"),
					Email:  c.String("commit-author-email"),
					Avatar: c.String("commit-author-avatar"),
				},
			},
		},
		Prev: frontend.Build{
			Number:   c.Int("prev-build-number"),
			Created:  c.Int64("prev-build-created"),
			Started:  c.Int64("prev-build-started"),
			Finished: c.Int64("prev-build-finished"),
			Status:   c.String("prev-build-status"),
			Event:    c.String("prev-build-event"),
			Link:     c.String("prev-build-link"),
			Commit: frontend.Commit{
				Sha:     c.String("prev-commit-sha"),
				Ref:     c.String("prev-commit-ref"),
				Refspec: c.String("prev-commit-refspec"),
				Branch:  c.String("prev-commit-branch"),
				Message: c.String("prev-commit-message"),
				Author: frontend.Author{
					Name:   c.String("prev-commit-author-name"),
					Email:  c.String("prev-commit-author-email"),
					Avatar: c.String("prev-commit-author-avatar"),
				},
			},
		},
		Job: frontend.Job{
			Number: c.Int("job-number"),
			// Matrix: ,
		},
		Sys: frontend.System{
			Name: c.String("system-name"),
			Link: c.String("system-link"),
			Arch: c.String("system-arch"),
		},
	}
}
