package main

import (
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/containers/image/types"
	"github.com/containers/libpod/cmd/podman/libpodruntime"
	"github.com/containers/libpod/cmd/podman/shared"
	"github.com/containers/libpod/libpod/image"
	"github.com/containers/libpod/pkg/util"
	"github.com/containers/libpod/utils"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

var (
	runlabelFlags = []cli.Flag{
		cli.StringFlag{
			Name:  "authfile",
			Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ",
		},
		cli.BoolFlag{
			Name:  "display",
			Usage: "preview the command that the label would run",
		},
		cli.StringFlag{
			Name:  "cert-dir",
			Usage: "`pathname` of a directory containing TLS certificates and keys",
		},
		cli.StringFlag{
			Name:  "creds",
			Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry",
		},
		cli.StringFlag{
			Name:  "name",
			Usage: "Assign a name to the container",
		},
		cli.StringFlag{
			Name:   "opt1",
			Usage:  "Optional parameter to pass for install",
			Hidden: true,
		},
		cli.StringFlag{
			Name:   "opt2",
			Usage:  "Optional parameter to pass for install",
			Hidden: true,
		},
		cli.StringFlag{
			Name:   "opt3",
			Usage:  "Optional parameter to pass for install",
			Hidden: true,
		},
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "Suppress output information when installing images",
		},
		cli.BoolFlag{
			Name:  "pull, p",
			Usage: "pull the image if it does not exist locally prior to executing the label contents",
		},
		cli.StringFlag{
			Name:  "signature-policy",
			Usage: "`pathname` of signature policy file (not usually used)",
		},
		cli.BoolTFlag{
			Name:  "tls-verify",
			Usage: "require HTTPS and verify certificates when contacting registries (default: true)",
		},
	}

	runlabelDescription = `
Executes a command as described by a container image label.
`
	runlabelCommand = cli.Command{
		Name:           "runlabel",
		Usage:          "Execute the command described by an image label",
		Description:    runlabelDescription,
		Flags:          sortFlags(runlabelFlags),
		Action:         runlabelCmd,
		ArgsUsage:      "",
		SkipArgReorder: true,
		OnUsageError:   usageErrorHandler,
	}
)

// installCmd gets the data from the command line and calls installImage
// to copy an image from a registry to a local machine
func runlabelCmd(c *cli.Context) error {
	var (
		imageName      string
		stdErr, stdOut io.Writer
		stdIn          io.Reader
		newImage       *image.Image
	)

	// Evil images could trick into recursively executing the runlabel
	// command.  Avoid this by setting the "PODMAN_RUNLABEL_NESTED" env
	// variable when executing a label first.
	nested := os.Getenv("PODMAN_RUNLABEL_NESTED")
	if nested == "1" {
		return fmt.Errorf("nested runlabel calls: runlabels cannot execute the runlabel command")
	}

	opts := make(map[string]string)
	runtime, err := libpodruntime.GetRuntime(c)
	if err != nil {
		return errors.Wrapf(err, "could not get runtime")
	}
	defer runtime.Shutdown(false)

	args := c.Args()
	if len(args) < 2 {
		logrus.Errorf("the runlabel command requires at least 2 arguments: LABEL IMAGE")
		return nil
	}
	if err := validateFlags(c, runlabelFlags); err != nil {
		return err
	}
	if c.Bool("display") && c.Bool("quiet") {
		return errors.Errorf("the display and quiet flags cannot be used together.")
	}

	pull := c.Bool("pull")
	label := args[0]

	runlabelImage := args[1]

	if c.IsSet("opt1") {
		opts["opt1"] = c.String("opt1")
	}
	if c.IsSet("opt2") {
		opts["opt2"] = c.String("opt2")
	}
	if c.IsSet("opt3") {
		opts["opt3"] = c.String("opt3")
	}

	ctx := getContext()

	stdErr = os.Stderr
	stdOut = os.Stdout
	stdIn = os.Stdin

	if c.Bool("quiet") {
		stdErr = nil
		stdOut = nil
		stdIn = nil
	}

	if pull {
		var registryCreds *types.DockerAuthConfig
		if c.IsSet("creds") {
			creds, err := util.ParseRegistryCreds(c.String("creds"))
			if err != nil {
				return err
			}
			registryCreds = creds
		}
		dockerRegistryOptions := image.DockerRegistryOptions{
			DockerRegistryCreds:         registryCreds,
			DockerCertPath:              c.String("cert-dir"),
			DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"),
		}
		authfile := getAuthFile(c.String("authfile"))

		newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, c.String("signature-policy"), authfile, stdOut, &dockerRegistryOptions, image.SigningOptions{}, false, false)
	} else {
		newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage)
	}
	if err != nil {
		return errors.Wrapf(err, "unable to find image")
	}

	if len(newImage.Names()) < 1 {
		imageName = newImage.ID()
	} else {
		imageName = newImage.Names()[0]
	}

	runLabel, err := newImage.GetLabel(ctx, label)
	if err != nil {
		return err
	}

	// If no label to execute, we return
	if runLabel == "" {
		return nil
	}

	// The user provided extra arguments that need to be tacked onto the label's command
	if len(args) > 2 {
		runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(args[2:], " "))
	}

	cmd := shared.GenerateCommand(runLabel, imageName, c.String("name"))
	env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts)
	env = append(env, "PODMAN_RUNLABEL_NESTED=1")

	envmap := envSliceToMap(env)

	envmapper := func(k string) string {
		switch k {
		case "OPT1":
			return envmap["OPT1"]
		case "OPT2":
			return envmap["OPT2"]
		case "OPT3":
			return envmap["OPT3"]
		}
		return ""
	}

	newS := os.Expand(strings.Join(cmd, " "), envmapper)
	cmd = strings.Split(newS, " ")

	if !c.Bool("quiet") {
		fmt.Printf("Command: %s\n", strings.Join(cmd, " "))
		if c.Bool("display") {
			return nil
		}
	}
	return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...)
}

func envSliceToMap(env []string) map[string]string {
	m := make(map[string]string)
	for _, i := range env {
		split := strings.Split(i, "=")
		m[split[0]] = strings.Join(split[1:], " ")
	}
	return m
}
