package start

import (
	"errors"
	"fmt"
	"io"
	"os"

	"github.com/coreos/go-systemd/daemon"
	"github.com/golang/glog"
	"github.com/spf13/cobra"

	kerrors "k8s.io/kubernetes/pkg/api/errors"
	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/util/validation/field"

	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest"
	"github.com/openshift/origin/pkg/cmd/server/api/validation"
	"github.com/openshift/origin/pkg/cmd/server/etcd/etcdserver"
	"github.com/openshift/origin/pkg/cmd/templates"
)

const RecommendedStartEtcdServerName = "etcd"

type EtcdOptions struct {
	ConfigFile string
	Output     io.Writer
}

var etcdLong = templates.LongDesc(`
	Start an etcd server for testing.

	This command starts an etcd server based on the config for testing.  It is not
	intended for production use.  Running

	    %[1]s start %[2]s

	will start the server listening for incoming requests. The server will run in
	the foreground until you terminate the process.`)

// NewCommandStartEtcdServer starts only the etcd server
func NewCommandStartEtcdServer(name, basename string, out, errout io.Writer) (*cobra.Command, *EtcdOptions) {
	options := &EtcdOptions{Output: out}

	cmd := &cobra.Command{
		Use:   name,
		Short: "Launch etcd server",
		Long:  fmt.Sprintf(etcdLong, basename, name),
		Run: func(c *cobra.Command, args []string) {
			kcmdutil.CheckErr(options.Validate())

			startProfiler()

			if err := options.StartEtcdServer(); err != nil {
				if kerrors.IsInvalid(err) {
					if details := err.(*kerrors.StatusError).ErrStatus.Details; details != nil {
						fmt.Fprintf(errout, "Invalid %s %s\n", details.Kind, details.Name)
						for _, cause := range details.Causes {
							fmt.Fprintf(errout, "  %s: %s\n", cause.Field, cause.Message)
						}
						os.Exit(255)
					}
				}
				glog.Fatal(err)
			}
		},
	}

	flags := cmd.Flags()
	// This command only supports reading from config
	flags.StringVar(&options.ConfigFile, "config", "", "Location of the master configuration file to run from.")
	cmd.MarkFlagFilename("config", "yaml", "yml")
	cmd.MarkFlagRequired("config")

	return cmd, options
}

func (o *EtcdOptions) Validate() error {
	if len(o.ConfigFile) == 0 {
		return errors.New("--config is required for this command")
	}

	return nil
}

// StartEtcdServer calls RunEtcdServer and then waits forever
func (o *EtcdOptions) StartEtcdServer() error {
	if err := o.RunEtcdServer(); err != nil {
		return err
	}

	go daemon.SdNotify("READY=1")
	select {}
}

// RunEtcdServer takes the options and starts the etcd server
func (o *EtcdOptions) RunEtcdServer() error {
	masterConfig, err := configapilatest.ReadAndResolveMasterConfig(o.ConfigFile)
	if err != nil {
		return err
	}

	validationResults := validation.ValidateMasterConfig(masterConfig, nil)
	if len(validationResults.Warnings) != 0 {
		for _, warning := range validationResults.Warnings {
			glog.Warningf("%v", warning)
		}
	}
	if len(validationResults.Errors) != 0 {
		return kerrors.NewInvalid(configapi.Kind("MasterConfig"), o.ConfigFile, validationResults.Errors)
	}

	if masterConfig.EtcdConfig == nil {
		return kerrors.NewInvalid(configapi.Kind("MasterConfig.EtcConfig"), o.ConfigFile, field.ErrorList{field.Required(field.NewPath("etcdConfig"), "")})
	}

	etcdserver.RunEtcd(masterConfig.EtcdConfig)
	return nil
}