package cmd

import (
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"time"

	etcdversion "github.com/coreos/etcd/version"

	kapierrors "k8s.io/kubernetes/pkg/api/errors"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	kclientcmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	kubeversion "k8s.io/kubernetes/pkg/version"

	"github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/cmd/templates"
	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
	"github.com/openshift/origin/pkg/cmd/util/tokencmd"
	"github.com/openshift/origin/pkg/version"

	"github.com/spf13/cobra"
)

var (
	versionLong = templates.LongDesc(`Display client and server versions.`)
)

type VersionOptions struct {
	BaseName string
	Out      io.Writer

	ClientConfig kclientcmd.ClientConfig
	Clients      func() (*client.Client, *kclient.Client, error)

	IsServer            bool
	PrintEtcdVersion    bool
	PrintClientFeatures bool
}

// NewCmdVersion creates a command for displaying the version of this binary
func NewCmdVersion(fullName string, f *clientcmd.Factory, out io.Writer, options VersionOptions) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "version",
		Short: "Display client and server versions",
		Long:  versionLong,
		Run: func(cmd *cobra.Command, args []string) {
			options.BaseName = fullName

			if err := options.Complete(f, out); err != nil {
				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
			}

			if err := options.RunVersion(); err != nil {
				kcmdutil.CheckErr(err)
			}
		},
	}

	return cmd
}

func (o *VersionOptions) Complete(f *clientcmd.Factory, out io.Writer) error {
	o.Out = out

	if f == nil {
		return nil
	}

	o.Clients = f.Clients
	o.ClientConfig = f.OpenShiftClientConfig
	return nil
}

// RunVersion attempts to display client and server versions for Kubernetes and OpenShift
func (o VersionOptions) RunVersion() error {
	fmt.Fprintf(o.Out, "%s %v\n", o.BaseName, version.Get())
	fmt.Fprintf(o.Out, "kubernetes %v\n", kubeversion.Get())
	if o.PrintEtcdVersion {
		fmt.Fprintf(o.Out, "etcd %v\n", etcdversion.Version)
	}

	if o.PrintClientFeatures {
		features := []string{}
		if tokencmd.BasicEnabled() {
			features = append(features, "Basic-Auth")
		}
		if tokencmd.GSSAPIEnabled() {
			features = append(features, "GSSAPI")
			features = append(features, "Kerberos") // GSSAPI or SSPI
			features = append(features, "SPNEGO")   // GSSAPI or SSPI
		}
		fmt.Printf("features: %s\n", strings.Join(features, " "))
	}

	// do not attempt to print server info if already running cmd as the server
	// or if no client config is present
	if o.ClientConfig == nil || o.IsServer {
		return nil
	}

	// max amount of time we want to wait for server to respond
	timeout := 10 * time.Second

	done := make(chan error)
	oVersion := ""
	kVersion := ""
	versionHost := ""

	// start goroutine to fetch openshift / kubernetes server version
	go func() {
		defer close(done)

		// confirm config exists before makig request to server
		var err error
		clientConfig, err := o.ClientConfig.ClientConfig()
		if err != nil {
			done <- err
			return
		}
		versionHost = clientConfig.Host

		oClient, kClient, err := o.Clients()
		if err != nil {
			done <- err
			return
		}

		kubeVersionBody, err := kClient.Get().AbsPath("/version").Do().Raw()
		switch {
		case err == nil:
			var kubeServerInfo kubeversion.Info
			err = json.Unmarshal(kubeVersionBody, &kubeServerInfo)
			if err != nil && len(kubeVersionBody) > 0 {
				done <- err
				return
			}
			kVersion = fmt.Sprintf("%v", kubeServerInfo)
		case kapierrors.IsNotFound(err) || kapierrors.IsUnauthorized(err) || kapierrors.IsForbidden(err):
		default:
			done <- err
			return
		}

		ocVersionBody, err := oClient.Get().AbsPath("/version/openshift").Do().Raw()
		switch {
		case err == nil:
			var ocServerInfo version.Info
			err = json.Unmarshal(ocVersionBody, &ocServerInfo)
			if err != nil && len(ocVersionBody) > 0 {
				done <- err
				return
			}
			oVersion = fmt.Sprintf("%v", ocServerInfo)
		case kapierrors.IsNotFound(err) || kapierrors.IsUnauthorized(err) || kapierrors.IsForbidden(err):
		default:
			done <- err
			return
		}
	}()

	select {
	case err, closed := <-done:
		if strings.HasSuffix(fmt.Sprintf("%v", err), "connection refused") || clientcmd.IsConfigurationMissing(err) || kclientcmd.IsConfigurationInvalid(err) {
			return nil
		}
		if closed && err != nil {
			return err
		}
	case <-time.After(timeout):
		return fmt.Errorf("%s", "error: server took too long to respond with version information.")
	}

	if oVersion != "" || kVersion != "" {
		fmt.Fprintf(o.Out, "\n%s%s\n", "Server ", versionHost)
	}
	if oVersion != "" {
		fmt.Fprintf(o.Out, "openshift %s\n", oVersion)
	}
	if kVersion != "" {
		fmt.Fprintf(o.Out, "kubernetes %s\n", kVersion)
	}

	return nil
}