package main

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

	"github.com/containers/image/types"
	"github.com/containers/libpod/cmd/podman/cliconfig"
	"github.com/containers/libpod/cmd/podman/libpodruntime"
	"github.com/containers/libpod/cmd/podman/shared"
	"github.com/containers/libpod/libpod"
	"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/spf13/cobra"
)

var (
	runlabelCommand     cliconfig.RunlabelValues
	runlabelDescription = `
Executes a command as described by a container image label.
`
	_runlabelCommand = &cobra.Command{
		Use:   "runlabel [flags] LABEL IMAGE [ARG...]",
		Short: "Execute the command described by an image label",
		Long:  runlabelDescription,
		RunE: func(cmd *cobra.Command, args []string) error {
			runlabelCommand.InputArgs = args
			runlabelCommand.GlobalFlags = MainGlobalOpts
			runlabelCommand.Remote = remoteclient
			return runlabelCmd(&runlabelCommand)
		},
		Example: `podman container runlabel run imageID
  podman container runlabel --pull install imageID arg1 arg2
  podman container runlabel --display run myImage`,
	}
)

func init() {
	runlabelCommand.Command = _runlabelCommand
	runlabelCommand.SetHelpTemplate(HelpTemplate())
	runlabelCommand.SetUsageTemplate(UsageTemplate())
	flags := runlabelCommand.Flags()
	flags.StringVar(&runlabelCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
	flags.BoolVar(&runlabelCommand.Display, "display", false, "Preview the command that the label would run")
	flags.BoolVar(&runlabelCommand.Replace, "replace", false, "Replace existing container with a new one from the image")
	flags.StringVar(&runlabelCommand.Name, "name", "", "Assign a name to the container")

	flags.StringVar(&runlabelCommand.Opt1, "opt1", "", "Optional parameter to pass for install")
	flags.StringVar(&runlabelCommand.Opt2, "opt2", "", "Optional parameter to pass for install")
	flags.StringVar(&runlabelCommand.Opt3, "opt3", "", "Optional parameter to pass for install")
	flags.MarkHidden("opt1")
	flags.MarkHidden("opt2")
	flags.MarkHidden("opt3")

	flags.BoolP("pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents")
	flags.BoolVarP(&runlabelCommand.Quiet, "quiet", "q", false, "Suppress output information when installing images")
	// Disabled flags for the remote client
	if !remote {
		flags.StringVar(&runlabelCommand.Authfile, "authfile", getAuthFile(""), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
		flags.StringVar(&runlabelCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys")
		flags.StringVar(&runlabelCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)")
		flags.BoolVar(&runlabelCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")

		flags.MarkDeprecated("pull", "podman will pull if not found in local storage")
		flags.MarkHidden("signature-policy")
	}
	markFlagHiddenForRemoteClient("authfile", flags)
}

// 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 *cliconfig.RunlabelValues) error {
	var (
		imageName      string
		stdErr, stdOut io.Writer
		stdIn          io.Reader
		extraArgs      []string
	)

	// 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(getContext(), &c.PodmanCommand)
	if err != nil {
		return errors.Wrapf(err, "could not get runtime")
	}
	defer runtime.Shutdown(false)

	args := c.InputArgs
	if len(args) < 2 {
		return errors.Errorf("the runlabel command requires at least 2 arguments: LABEL IMAGE")
	}
	if c.Display && c.Quiet {
		return errors.Errorf("the display and quiet flags cannot be used together.")
	}

	if len(args) > 2 {
		extraArgs = args[2:]
	}
	label := args[0]

	runlabelImage := args[1]

	if c.Flag("opt1").Changed {
		opts["opt1"] = c.Opt1
	}

	if c.Flag("opt2").Changed {
		opts["opt2"] = c.Opt2
	}
	if c.Flag("opt3").Changed {
		opts["opt3"] = c.Opt3
	}

	ctx := getContext()

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

	if c.Quiet {
		stdErr = nil
		stdOut = nil
		stdIn = nil
	}

	dockerRegistryOptions := image.DockerRegistryOptions{
		DockerCertPath: c.CertDir,
	}
	if c.Flag("tls-verify").Changed {
		dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify)
	}

	runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, true, c.Creds, dockerRegistryOptions, c.Authfile, c.SignaturePolicy, stdOut)
	if err != nil {
		return err
	}
	if runLabel == "" {
		return errors.Errorf("%s does not have a label of %s", runlabelImage, label)
	}

	globalOpts := util.GetGlobalOpts(c)
	cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.Name, opts, extraArgs, globalOpts)
	if err != nil {
		return err
	}
	if !c.Quiet {
		fmt.Printf("command: %s\n", strings.Join(append([]string{os.Args[0]}, cmd[1:]...), " "))
		if c.Display {
			return nil
		}
	}

	// If container already exists && --replace given -- Nuke it
	if c.Replace {
		for i, entry := range cmd {
			if entry == "--name" {
				name := cmd[i+1]
				ctr, err := runtime.LookupContainer(name)
				if err != nil {
					if errors.Cause(err) != libpod.ErrNoSuchCtr {
						logrus.Debugf("Error occurred searching for container %s: %s", name, err.Error())
						return err
					}
				} else {
					logrus.Debugf("Runlabel --replace option given. Container %s will be deleted. The new container will be named %s", ctr.ID(), name)
					if err := runtime.RemoveContainer(ctx, ctr, true, false); err != nil {
						return err
					}
				}
				break
			}
		}
	}

	return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...)
}