// +build integration,!no-etcd

package integration

import (
	"fmt"
	"net/http/httptest"
	"os"
	"path"
	"time"

	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"

	"github.com/openshift/origin/pkg/client"
	newproject "github.com/openshift/origin/pkg/cmd/experimental/project"
	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/cmd/server/start"
	cmdutil "github.com/openshift/origin/pkg/cmd/util"
	"github.com/openshift/origin/pkg/cmd/util/tokencmd"
)

func init() {
	requireEtcd()
}

func setupStartOptions() (*start.MasterArgs, *start.NodeArgs, *start.BindAddrArg, *start.ImageFormatArgs, *start.KubeConnectionArgs, *start.CertArgs) {
	masterArgs, nodeArgs, bindAddrArg, imageFormatArgs, kubeConnectionArgs, certArgs := start.GetAllInOneArgs()

	basedir := path.Join(os.TempDir(), "openshift-integration-tests")
	nodeArgs.VolumeDir = path.Join(basedir, "volume")
	masterArgs.EtcdDir = path.Join(basedir, "etcd")
	certArgs.CertDir = path.Join(basedir, "cert")

	// don't wait for nodes to come up

	masterAddr := httptest.NewUnstartedServer(nil).Listener.Addr().String()
	fmt.Printf("masterAddr: %#v\n", masterAddr)
	masterArgs.MasterAddr.Set(masterAddr)
	bindAddrArg.BindAddr.Set(masterAddr)
	masterArgs.EtcdAddr.Set(getEtcdURL())

	assetAddr := httptest.NewUnstartedServer(nil).Listener.Addr().String()
	fmt.Printf("assetAddr: %#v\n", assetAddr)
	masterArgs.AssetBindAddr.Set(assetAddr)
	masterArgs.AssetPublicAddr.Set(assetAddr)

	dnsAddr := httptest.NewUnstartedServer(nil).Listener.Addr().String()
	fmt.Printf("dnsAddr: %#v\n", dnsAddr)
	masterArgs.DNSBindAddr.Set(dnsAddr)

	return masterArgs, nodeArgs, bindAddrArg, imageFormatArgs, kubeConnectionArgs, certArgs
}

func getAdminKubeConfigFile(certArgs start.CertArgs) string {
	return path.Clean(path.Join(certArgs.CertDir, "admin/.kubeconfig"))
}

func StartTestAllInOne() (*configapi.MasterConfig, string, error) {
	deleteAllEtcdKeys()

	masterArgs, nodeArgs, _, _, _, _ := setupStartOptions()
	masterArgs.NodeList = nil

	startOptions := start.AllInOneOptions{}
	startOptions.MasterArgs, startOptions.NodeArgs = masterArgs, nodeArgs
	startOptions.Complete()

	errCh := make(chan error)
	go func() {
		errCh <- startOptions.StartAllInOne()
		close(errCh)
	}()

	adminKubeConfigFile := getAdminKubeConfigFile(*masterArgs.CertArgs)

	openshiftMasterConfig, err := startOptions.MasterArgs.BuildSerializeableMasterConfig()
	if err != nil {
		return nil, "", err
	}

	// wait for the server to come up: 35 seconds
	if err := cmdutil.WaitForSuccessfulDial(true, "tcp", masterArgs.MasterAddr.URL.Host, 100*time.Millisecond, 1*time.Second, 35); err != nil {
		select {
		case err := <-errCh:
			if err != nil {
				return nil, "", err
			}
		default:
		}
		return nil, "", err
	}

	// try to get a client
	for {
		select {
		case err := <-errCh:
			if err != nil {
				return nil, "", err
			}
		default:
		}
		// confirm that we can actually query from the api server

		if client, _, _, err := GetClusterAdminClient(adminKubeConfigFile); err == nil {
			if _, err := client.Policies("master").List(labels.Everything(), labels.Everything()); err == nil {
				break
			}
		}
		time.Sleep(100 * time.Millisecond)
	}
	return openshiftMasterConfig, adminKubeConfigFile, nil
}

// TODO Unify with StartAllInOne.

// StartTestMaster starts up a test master and returns back the startOptions so you can get clients and certs
func StartTestMaster() (*configapi.MasterConfig, string, error) {
	deleteAllEtcdKeys()

	masterArgs, _, _, _, _, _ := setupStartOptions()

	startOptions := start.MasterOptions{}
	startOptions.MasterArgs = masterArgs
	startOptions.Complete()

	var startError error
	go func() {
		err := startOptions.StartMaster()
		if err != nil {
			startError = err
			fmt.Printf("ERROR STARTING SERVER! %v", err)
		}
	}()

	adminKubeConfigFile := getAdminKubeConfigFile(*masterArgs.CertArgs)

	openshiftMasterConfig, err := startOptions.MasterArgs.BuildSerializeableMasterConfig()
	if err != nil {
		return nil, "", err
	}

	// wait for the server to come up: 35 seconds
	if err := cmdutil.WaitForSuccessfulDial(true, "tcp", masterArgs.MasterAddr.URL.Host, 100*time.Millisecond, 1*time.Second, 35); err != nil {
		return nil, "", err
	}

	stopChannel := make(chan struct{})
	util.Until(
		func() {
			if startError != nil {
				close(stopChannel)
				return
			}

			// confirm that we can actually query from the api server
			client, _, _, err := GetClusterAdminClient(adminKubeConfigFile)
			if err != nil {
				return
			}
			if _, err := client.Policies("master").List(labels.Everything(), labels.Everything()); err == nil {
				close(stopChannel)
			}
		}, 100*time.Millisecond, stopChannel)

	return openshiftMasterConfig, adminKubeConfigFile, startError
}

// CreateNewProject creates a new project using the clusterAdminClient, then gets a token for the adminUser and returns
// back a client for the admin user
func CreateNewProject(clusterAdminClient *client.Client, clientConfig kclient.Config, projectName, adminUser string) (*client.Client, error) {
	qualifiedUser := "anypassword:" + adminUser
	newProjectOptions := &newproject.NewProjectOptions{
		Client:                clusterAdminClient,
		ProjectName:           projectName,
		AdminRole:             "admin",
		MasterPolicyNamespace: "master",
		AdminUser:             qualifiedUser,
	}

	if err := newProjectOptions.Run(); err != nil {
		return nil, err
	}

	token, err := tokencmd.RequestToken(&clientConfig, nil, adminUser, "password")
	if err != nil {
		return nil, err
	}

	adminClientConfig := clientConfig
	adminClientConfig.BearerToken = token
	adminClientConfig.Username = ""
	adminClientConfig.Password = ""
	adminClientConfig.TLSClientConfig.CertFile = ""
	adminClientConfig.TLSClientConfig.KeyFile = ""
	adminClientConfig.TLSClientConfig.CertData = nil
	adminClientConfig.TLSClientConfig.KeyData = nil

	adminClient, err := client.New(&adminClientConfig)
	if err != nil {
		return nil, err
	}

	return adminClient, nil
}

func GetClusterAdminClient(adminKubeConfigFile string) (*client.Client, *kclient.Client, *kclient.Config, error) {
	kclient, clientConfig, err := configapi.GetKubeClient(adminKubeConfigFile)
	if err != nil {
		return nil, nil, nil, err
	}

	osclient, err := client.New(clientConfig)
	if err != nil {
		return nil, nil, nil, err
	}

	return osclient, kclient, clientConfig, nil
}