package cmd

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"regexp"
	"text/template"

	"github.com/runconduit/conduit/cli/install"
	"github.com/runconduit/conduit/pkg/k8s"
	uuid "github.com/satori/go.uuid"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

type installConfig struct {
	Namespace                string
	ControllerImage          string
	WebImage                 string
	PrometheusImage          string
	GrafanaImage             string
	ControllerReplicas       uint
	WebReplicas              uint
	PrometheusReplicas       uint
	ImagePullPolicy          string
	UUID                     string
	CliVersion               string
	ControllerLogLevel       string
	ControllerComponentLabel string
	CreatedByAnnotation      string
	ProxyAPIPort             uint
}

type installOptions struct {
	dockerRegistry     string
	controllerReplicas uint
	webReplicas        uint
	prometheusReplicas uint
	controllerLogLevel string
	*proxyConfigOptions
}

func newInstallOptions() *installOptions {
	return &installOptions{
		dockerRegistry:     "gcr.io/runconduit",
		controllerReplicas: 1,
		webReplicas:        1,
		prometheusReplicas: 1,
		controllerLogLevel: "info",
		proxyConfigOptions: newProxyConfigOptions(),
	}
}

func newCmdInstall() *cobra.Command {
	options := newInstallOptions()

	cmd := &cobra.Command{
		Use:   "install [flags]",
		Short: "Output Kubernetes configs to install Conduit",
		Long:  "Output Kubernetes configs to install Conduit.",
		RunE: func(cmd *cobra.Command, args []string) error {
			config, err := validateAndBuildConfig(options)
			if err != nil {
				return err
			}
			return render(*config, os.Stdout, options)
		},
	}

	addProxyConfigFlags(cmd, options.proxyConfigOptions)
	cmd.PersistentFlags().StringVar(&options.dockerRegistry, "registry", options.dockerRegistry, "Docker registry to pull images from")
	cmd.PersistentFlags().UintVar(&options.controllerReplicas, "controller-replicas", options.controllerReplicas, "Replicas of the controller to deploy")
	cmd.PersistentFlags().UintVar(&options.webReplicas, "web-replicas", options.webReplicas, "Replicas of the web server to deploy")
	cmd.PersistentFlags().UintVar(&options.prometheusReplicas, "prometheus-replicas", options.prometheusReplicas, "Replicas of prometheus to deploy")
	cmd.PersistentFlags().StringVar(&options.controllerLogLevel, "controller-log-level", options.controllerLogLevel, "Log level for the controller and web components")

	return cmd
}

func validateAndBuildConfig(options *installOptions) (*installConfig, error) {
	if err := validate(options); err != nil {
		return nil, err
	}
	return &installConfig{
		Namespace:                controlPlaneNamespace,
		ControllerImage:          fmt.Sprintf("%s/controller:%s", options.dockerRegistry, options.conduitVersion),
		WebImage:                 fmt.Sprintf("%s/web:%s", options.dockerRegistry, options.conduitVersion),
		PrometheusImage:          "prom/prometheus:v2.2.1",
		GrafanaImage:             fmt.Sprintf("%s/grafana:%s", options.dockerRegistry, options.conduitVersion),
		ControllerReplicas:       options.controllerReplicas,
		WebReplicas:              options.webReplicas,
		PrometheusReplicas:       options.prometheusReplicas,
		ImagePullPolicy:          options.imagePullPolicy,
		UUID:                     uuid.NewV4().String(),
		CliVersion:               k8s.CreatedByAnnotationValue(),
		ControllerLogLevel:       options.controllerLogLevel,
		ControllerComponentLabel: k8s.ControllerComponentLabel,
		CreatedByAnnotation:      k8s.CreatedByAnnotation,
		ProxyAPIPort:             options.proxyAPIPort,
	}, nil
}

func render(config installConfig, w io.Writer, options *installOptions) error {
	template, err := template.New("conduit").Parse(install.Template)
	if err != nil {
		return err
	}
	buf := &bytes.Buffer{}
	err = template.Execute(buf, config)
	if err != nil {
		return err
	}
	injectOptions := newInjectOptions()
	injectOptions.proxyConfigOptions = options.proxyConfigOptions
	return InjectYAML(buf, w, injectOptions)
}

var alphaNumDash = regexp.MustCompile("^[a-zA-Z0-9-]+$")
var alphaNumDashDot = regexp.MustCompile("^[\\.a-zA-Z0-9-]+$")
var alphaNumDashDotSlash = regexp.MustCompile("^[\\./a-zA-Z0-9-]+$")

func validate(options *installOptions) error {
	// These regexs are not as strict as they could be, but are a quick and dirty
	// sanity check against illegal characters.
	if !alphaNumDash.MatchString(controlPlaneNamespace) {
		return fmt.Errorf("%s is not a valid namespace", controlPlaneNamespace)
	}
	if !alphaNumDashDot.MatchString(options.conduitVersion) {
		return fmt.Errorf("%s is not a valid version", options.conduitVersion)
	}
	if !alphaNumDashDotSlash.MatchString(options.dockerRegistry) {
		return fmt.Errorf("%s is not a valid Docker registry", options.dockerRegistry)
	}
	if options.imagePullPolicy != "Always" && options.imagePullPolicy != "IfNotPresent" && options.imagePullPolicy != "Never" {
		return fmt.Errorf("--image-pull-policy must be one of: Always, IfNotPresent, Never")
	}
	if _, err := log.ParseLevel(options.controllerLogLevel); err != nil {
		return fmt.Errorf("--controller-log-level must be one of: panic, fatal, error, warn, info, debug")
	}
	return nil
}
