package start

import (
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"testing"

	"github.com/spf13/cobra"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	utilerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"

	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/cmd/server/api/validation"
)

func TestCommandBindingListenHttp(t *testing.T) {
	valueToSet := "http://example.org:9123"
	masterArgs, masterCfg, masterErr, nodeArgs, nodeCfg, nodeErr := executeAllInOneCommandWithConfigs([]string{"--listen=" + valueToSet})

	if masterErr != nil {
		t.Fatalf("Unexpected error: %v", masterErr)
	}
	if nodeErr != nil {
		t.Fatalf("Unexpected error: %v", nodeErr)
	}

	if configapi.UseTLS(masterCfg.ServingInfo) {
		t.Errorf("Unexpected TLS: %v", masterCfg.ServingInfo)
	}
	if configapi.UseTLS(masterCfg.AssetConfig.ServingInfo) {
		t.Errorf("Unexpected TLS: %v", masterCfg.AssetConfig.ServingInfo)
	}
	if configapi.UseTLS(nodeCfg.ServingInfo) {
		t.Errorf("Unexpected TLS: %v", nodeCfg.ServingInfo)
	}

	if masterArgs.ListenArg.ListenAddr.String() != valueToSet {
		t.Errorf("Expected %v, got %v", valueToSet, masterArgs.ListenArg.ListenAddr.String())
	}
	if nodeArgs.ListenArg.ListenAddr.String() != valueToSet {
		t.Errorf("Expected %v, got %v", valueToSet, nodeArgs.ListenArg.ListenAddr.String())
	}

	// Ensure there are no errors other than missing client kubeconfig files and missing bootstrap policy files
	masterErrs := validation.ValidateMasterConfig(masterCfg).Filter(func(e error) bool {
		return strings.Contains(e.Error(), "masterClients.") || strings.Contains(e.Error(), "policyConfig.bootstrapPolicyFile")
	})
	if len(masterErrs) != 0 {
		t.Errorf("Unexpected validation errors: %v", utilerrors.NewAggregate(masterErrs))
	}

	nodeErrs := validation.ValidateNodeConfig(nodeCfg).Filter(func(e error) bool {
		return strings.Contains(e.Error(), "masterKubeConfig")
	})
	if len(nodeErrs) != 0 {
		t.Errorf("Unexpected validation errors: %v", utilerrors.NewAggregate(nodeErrs))
	}
}

func TestCommandBindingListen(t *testing.T) {
	valueToSet := "http://example.org:9123"
	actualCfg := executeMasterCommand([]string{"--listen=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.ListenArg.ListenAddr.Set(valueToSet)

	if expectedArgs.ListenArg.ListenAddr.String() != actualCfg.ListenArg.ListenAddr.String() {
		t.Errorf("expected %v, got %v", expectedArgs.ListenArg.ListenAddr.String(), actualCfg.ListenArg.ListenAddr.String())
	}
}

func TestCommandBindingMaster(t *testing.T) {
	valueToSet := "http://example.org:9123"
	actualCfg := executeMasterCommand([]string{"--master=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.MasterAddr.Set(valueToSet)

	if expectedArgs.MasterAddr.String() != actualCfg.MasterAddr.String() {
		t.Errorf("expected %v, got %v", expectedArgs.MasterAddr.String(), actualCfg.MasterAddr.String())
	}
}

func TestCommandBindingMasterPublic(t *testing.T) {
	valueToSet := "http://example.org:9123"
	actualCfg := executeMasterCommand([]string{"--public-master=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.MasterPublicAddr.Set(valueToSet)

	if expectedArgs.MasterPublicAddr.String() != actualCfg.MasterPublicAddr.String() {
		t.Errorf("expected %v, got %v", expectedArgs.MasterPublicAddr.String(), actualCfg.MasterPublicAddr.String())
	}
}

func TestCommandBindingEtcd(t *testing.T) {
	valueToSet := "http://example.org:9123"
	actualCfg := executeMasterCommand([]string{"--etcd=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.EtcdAddr.Set(valueToSet)

	if expectedArgs.EtcdAddr.String() != actualCfg.EtcdAddr.String() {
		t.Errorf("expected %v, got %v", expectedArgs.EtcdAddr.String(), actualCfg.EtcdAddr.String())
	}
}

func TestCommandBindingKubernetes(t *testing.T) {
	valueToSet := "http://example.org:9123"
	actualCfg := executeMasterCommand([]string{"--kubernetes=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.KubeConnectionArgs.KubernetesAddr.Set(valueToSet)

	if expectedArgs.KubeConnectionArgs.KubernetesAddr.String() != actualCfg.KubeConnectionArgs.KubernetesAddr.String() {
		t.Errorf("expected %v, got %v", expectedArgs.KubeConnectionArgs.KubernetesAddr.String(), actualCfg.KubeConnectionArgs.KubernetesAddr.String())
	}
}

func TestCommandBindingKubernetesPublic(t *testing.T) {
	valueToSet := "http://example.org:9123"
	actualCfg := executeMasterCommand([]string{"--public-kubernetes=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.KubernetesPublicAddr.Set(valueToSet)

	if expectedArgs.KubernetesPublicAddr.String() != actualCfg.KubernetesPublicAddr.String() {
		t.Errorf("expected %v, got %v", expectedArgs.KubernetesPublicAddr.String(), actualCfg.KubernetesPublicAddr.String())
	}
}

func TestCommandBindingPortalNet(t *testing.T) {
	valueToSet := "192.168.0.0/16"
	actualCfg := executeMasterCommand([]string{"--portal-net=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.PortalNet.Set(valueToSet)

	if expectedArgs.PortalNet.String() != actualCfg.PortalNet.String() {
		t.Errorf("expected %v, got %v", expectedArgs.PortalNet.String(), actualCfg.PortalNet.String())
	}
}

func TestCommandBindingImageTemplateFormat(t *testing.T) {
	valueToSet := "some-format-string"
	actualCfg := executeMasterCommand([]string{"--images=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.ImageFormatArgs.ImageTemplate.Format = valueToSet

	if expectedArgs.ImageFormatArgs.ImageTemplate.Format != actualCfg.ImageFormatArgs.ImageTemplate.Format {
		t.Errorf("expected %v, got %v", expectedArgs.ImageFormatArgs.ImageTemplate.Format, actualCfg.ImageFormatArgs.ImageTemplate.Format)
	}
}

func TestCommandBindingImageLatest(t *testing.T) {
	expectedArgs := NewDefaultMasterArgs()

	valueToSet := strconv.FormatBool(!expectedArgs.ImageFormatArgs.ImageTemplate.Latest)
	actualCfg := executeMasterCommand([]string{"--latest-images=" + valueToSet})

	expectedArgs.ImageFormatArgs.ImageTemplate.Latest = !expectedArgs.ImageFormatArgs.ImageTemplate.Latest

	if expectedArgs.ImageFormatArgs.ImageTemplate.Latest != actualCfg.ImageFormatArgs.ImageTemplate.Latest {
		t.Errorf("expected %v, got %v", expectedArgs.ImageFormatArgs.ImageTemplate.Latest, actualCfg.ImageFormatArgs.ImageTemplate.Latest)
	}
}

func TestCommandBindingVolumeDir(t *testing.T) {
	valueToSet := "some-string"
	actualCfg := executeNodeCommand([]string{"--volume-dir=" + valueToSet})

	expectedArgs := NewDefaultNodeArgs()
	expectedArgs.VolumeDir = valueToSet

	if expectedArgs.VolumeDir != actualCfg.VolumeDir {
		t.Errorf("expected %v, got %v", expectedArgs.VolumeDir, actualCfg.VolumeDir)
	}
}

func TestCommandBindingEtcdDir(t *testing.T) {
	valueToSet := "some-string"
	actualCfg := executeMasterCommand([]string{"--etcd-dir=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.EtcdDir = valueToSet

	if expectedArgs.EtcdDir != actualCfg.EtcdDir {
		t.Errorf("expected %v, got %v", expectedArgs.EtcdDir, actualCfg.EtcdDir)
	}
}

func TestCommandBindingCertDir(t *testing.T) {
	valueToSet := "some-string"
	actualCfg := executeMasterCommand([]string{"--cert-dir=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.CertArgs.CertDir = valueToSet

	if expectedArgs.CertArgs.CertDir != actualCfg.CertArgs.CertDir {
		t.Errorf("expected %v, got %v", expectedArgs.CertArgs.CertDir, actualCfg.CertArgs.CertDir)
	}
}

func TestCommandBindingHostname(t *testing.T) {
	valueToSet := "some-string"
	actualCfg := executeNodeCommand([]string{"--hostname=" + valueToSet})

	expectedArgs := NewDefaultNodeArgs()
	expectedArgs.NodeName = valueToSet

	if expectedArgs.NodeName != actualCfg.NodeName {
		t.Errorf("expected %v, got %v", expectedArgs.NodeName, actualCfg.NodeName)
	}
}

// AllInOne always adds the default hostname
func TestCommandBindingNodesForAllInOneAppend(t *testing.T) {
	valueToSet := "first,second,third"
	actualMasterCfg, actualNodeConfig := executeAllInOneCommand([]string{"--nodes=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()

	stringList := util.StringList{}
	stringList.Set(valueToSet + "," + strings.ToLower(actualNodeConfig.NodeName))
	expectedArgs.NodeList.Set(strings.Join(util.NewStringSet(stringList...).List(), ","))

	if expectedArgs.NodeList.String() != actualMasterCfg.NodeList.String() {
		t.Errorf("expected %v, got %v", expectedArgs.NodeList, actualMasterCfg.NodeList)
	}
}

// AllInOne always adds the default hostname
func TestCommandBindingNodesForAllInOneAppendNoDupes(t *testing.T) {
	valueToSet := "first,localhost,second,third"
	actualMasterCfg, _ := executeAllInOneCommand([]string{"--nodes=" + valueToSet, "--hostname=LOCALHOST"})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.NodeList.Set(valueToSet)

	util.NewStringSet()

	if expectedArgs.NodeList.String() != actualMasterCfg.NodeList.String() {
		t.Errorf("expected %v, got %v", expectedArgs.NodeList, actualMasterCfg.NodeList)
	}
}

// AllInOne always adds the default hostname
func TestCommandBindingNodesDefaultingAllInOne(t *testing.T) {
	actualMasterCfg, _ := executeAllInOneCommand([]string{})

	expectedArgs := NewDefaultMasterArgs()
	expectedNodeArgs := NewDefaultNodeArgs()
	expectedArgs.NodeList.Set(strings.ToLower(expectedNodeArgs.NodeName))

	if expectedArgs.NodeList.String() != actualMasterCfg.NodeList.String() {
		t.Errorf("expected %v, got %v", expectedArgs.NodeList, actualMasterCfg.NodeList)
	}
}

// explicit start master never modifies the NodeList
func TestCommandBindingNodesForMaster(t *testing.T) {
	valueToSet := "first,second,third"
	actualCfg := executeMasterCommand([]string{"master", "--nodes=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.NodeList.Set(valueToSet)

	if expectedArgs.NodeList.String() != actualCfg.NodeList.String() {
		t.Errorf("expected %v, got %v", expectedArgs.NodeList, actualCfg.NodeList)
	}
}

// explicit start master never modifies the NodeList
func TestCommandBindingNodesDefaultingMaster(t *testing.T) {
	actualCfg := executeMasterCommand([]string{"master"})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.NodeList.Set("")

	if expectedArgs.NodeList.String() != actualCfg.NodeList.String() {
		t.Errorf("expected %v, got %v", expectedArgs.NodeList, actualCfg.NodeList)
	}
}

func TestCommandBindingCors(t *testing.T) {
	valueToSet := "first,second,third"
	actualCfg := executeMasterCommand([]string{"--cors-allowed-origins=" + valueToSet})

	expectedArgs := NewDefaultMasterArgs()
	expectedArgs.CORSAllowedOrigins.Set(valueToSet)

	if expectedArgs.CORSAllowedOrigins.String() != actualCfg.CORSAllowedOrigins.String() {
		t.Errorf("expected %v, got %v", expectedArgs.CORSAllowedOrigins, actualCfg.CORSAllowedOrigins)
	}
}

func executeMasterCommand(args []string) *MasterArgs {
	fakeConfigFile, _ := ioutil.TempFile("", "")
	defer os.Remove(fakeConfigFile.Name())

	argsToUse := make([]string, 0, 4+len(args))
	argsToUse = append(argsToUse, "master")
	argsToUse = append(argsToUse, args...)
	argsToUse = append(argsToUse, "--write-config")
	argsToUse = append(argsToUse, "--create-policy-file=false")
	argsToUse = append(argsToUse, "--create-certs=false")
	argsToUse = append(argsToUse, "--config="+fakeConfigFile.Name())

	root := &cobra.Command{
		Use:   "openshift",
		Short: "test",
		Long:  "",
		Run: func(c *cobra.Command, args []string) {
			c.Help()
		},
	}

	openshiftStartCommand, cfg := NewCommandStartMaster()
	root.AddCommand(openshiftStartCommand)
	root.SetArgs(argsToUse)
	root.Execute()

	return cfg.MasterArgs
}

func executeAllInOneCommand(args []string) (*MasterArgs, *NodeArgs) {
	masterArgs, _, _, nodeArgs, _, _ := executeAllInOneCommandWithConfigs(args)
	return masterArgs, nodeArgs
}

func executeAllInOneCommandWithConfigs(args []string) (*MasterArgs, *configapi.MasterConfig, error, *NodeArgs, *configapi.NodeConfig, error) {
	fakeMasterConfigFile, _ := ioutil.TempFile("", "")
	defer os.Remove(fakeMasterConfigFile.Name())
	fakeNodeConfigFile, _ := ioutil.TempFile("", "")
	defer os.Remove(fakeNodeConfigFile.Name())

	argsToUse := make([]string, 0, 4+len(args))
	argsToUse = append(argsToUse, "start")
	argsToUse = append(argsToUse, args...)
	argsToUse = append(argsToUse, "--write-config")
	argsToUse = append(argsToUse, "--create-certs=false")
	argsToUse = append(argsToUse, "--create-policy-file=false")
	argsToUse = append(argsToUse, "--master-config="+fakeMasterConfigFile.Name())
	argsToUse = append(argsToUse, "--node-config="+fakeNodeConfigFile.Name())

	root := &cobra.Command{
		Use:   "openshift",
		Short: "test",
		Long:  "",
		Run: func(c *cobra.Command, args []string) {
			c.Help()
		},
	}

	openshiftStartCommand, cfg := NewCommandStartAllInOne()
	root.AddCommand(openshiftStartCommand)
	root.SetArgs(argsToUse)
	root.Execute()

	masterCfg, masterErr := ReadMasterConfig(fakeMasterConfigFile.Name())
	nodeCfg, nodeErr := ReadNodeConfig(fakeNodeConfigFile.Name())

	return cfg.MasterArgs, masterCfg, masterErr, cfg.NodeArgs, nodeCfg, nodeErr
}

func executeNodeCommand(args []string) *NodeArgs {
	fakeConfigFile, _ := ioutil.TempFile("", "")
	defer os.Remove(fakeConfigFile.Name())

	argsToUse := make([]string, 0, 4+len(args))
	argsToUse = append(argsToUse, "node")
	argsToUse = append(argsToUse, args...)
	argsToUse = append(argsToUse, "--write-config")
	argsToUse = append(argsToUse, "--create-certs=false")
	argsToUse = append(argsToUse, "--config="+fakeConfigFile.Name())

	root := &cobra.Command{
		Use:   "openshift",
		Short: "test",
		Long:  "",
		Run: func(c *cobra.Command, args []string) {
			c.Help()
		},
	}

	openshiftStartCommand, cfg := NewCommandStartNode()
	root.AddCommand(openshiftStartCommand)
	root.SetArgs(argsToUse)
	root.Execute()

	return cfg.NodeArgs
}