package integration

import (
	"reflect"
	"strings"
	"testing"
	"time"

	kapi "k8s.io/kubernetes/pkg/api"
	kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	"k8s.io/kubernetes/pkg/fields"
	watchapi "k8s.io/kubernetes/pkg/watch"

	defaultsapi "github.com/openshift/origin/pkg/build/admission/defaults/api"
	overridesapi "github.com/openshift/origin/pkg/build/admission/overrides/api"
	buildtestutil "github.com/openshift/origin/pkg/build/admission/testutil"
	buildapi "github.com/openshift/origin/pkg/build/api"
	"github.com/openshift/origin/pkg/client"
	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
	testutil "github.com/openshift/origin/test/util"
	testserver "github.com/openshift/origin/test/util/server"
)

var buildPodAdmissionTestTimeout time.Duration = 30 * time.Second

func TestBuildDefaultGitHTTPProxy(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	httpProxy := "http://my.test.proxy:12345"
	oclient, kclientset := setupBuildDefaultsAdmissionTest(t, &defaultsapi.BuildDefaultsConfig{
		GitHTTPProxy: httpProxy,
	})
	build, _ := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := build.Spec.Source.Git.HTTPProxy; actual == nil || *actual != httpProxy {
		t.Errorf("Resulting build did not get expected HTTP proxy: %v", actual)
	}
}

func TestBuildDefaultGitHTTPSProxy(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	httpsProxy := "https://my.test.proxy:12345"
	oclient, kclientset := setupBuildDefaultsAdmissionTest(t, &defaultsapi.BuildDefaultsConfig{
		GitHTTPSProxy: httpsProxy,
	})
	build, _ := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := build.Spec.Source.Git.HTTPSProxy; actual == nil || *actual != httpsProxy {
		t.Errorf("Resulting build did not get expected HTTPS proxy: %v", actual)
	}
}

func TestBuildDefaultEnvironment(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	env := []kapi.EnvVar{
		{
			Name:  "VAR1",
			Value: "VALUE1",
		},
		{
			Name:  "VAR2",
			Value: "VALUE2",
		},
	}
	oclient, kclientset := setupBuildDefaultsAdmissionTest(t, &defaultsapi.BuildDefaultsConfig{
		Env: env,
	})
	build, _ := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := build.Spec.Strategy.DockerStrategy.Env; !reflect.DeepEqual(env, actual) {
		t.Errorf("Resulting build did not get expected environment: %v", actual)
	}
}

func TestBuildDefaultLabels(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	labels := []buildapi.ImageLabel{{Name: "KEY", Value: "VALUE"}}
	oclient, kclientset := setupBuildDefaultsAdmissionTest(t, &defaultsapi.BuildDefaultsConfig{
		ImageLabels: labels,
	})
	build, _ := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := build.Spec.Output.ImageLabels; !reflect.DeepEqual(labels, actual) {
		t.Errorf("Resulting build did not get expected labels: %v", actual)
	}
}

func TestBuildDefaultNodeSelectors(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	selectors := map[string]string{"KEY": "VALUE"}
	oclient, kclientset := setupBuildDefaultsAdmissionTest(t, &defaultsapi.BuildDefaultsConfig{
		NodeSelector: selectors,
	})
	_, pod := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := pod.Spec.NodeSelector; !reflect.DeepEqual(selectors, actual) {
		t.Errorf("Resulting pod did not get expected nodeselectors: %v", actual)
	}
}

func TestBuildDefaultAnnotations(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	annotations := map[string]string{"KEY": "VALUE"}
	oclient, kclientset := setupBuildDefaultsAdmissionTest(t, &defaultsapi.BuildDefaultsConfig{
		Annotations: annotations,
	})
	_, pod := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := pod.Annotations; strings.Compare(actual["KEY"], annotations["KEY"]) != 0 {
		t.Errorf("Resulting pod did not get expected annotations: actual: %v, expected: %v", actual["KEY"], annotations["KEY"])
	}
}

func TestBuildOverrideForcePull(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	oclient, kclientset := setupBuildOverridesAdmissionTest(t, &overridesapi.BuildOverridesConfig{
		ForcePull: true,
	})
	build, _ := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if !build.Spec.Strategy.DockerStrategy.ForcePull {
		t.Errorf("ForcePull was not set on resulting build")
	}
}

func TestBuildOverrideForcePullCustomStrategy(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	oclient, kclientset := setupBuildOverridesAdmissionTest(t, &overridesapi.BuildOverridesConfig{
		ForcePull: true,
	})
	build, pod := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestCustomBuild())
	if pod.Spec.Containers[0].ImagePullPolicy != kapi.PullAlways {
		t.Errorf("Pod ImagePullPolicy is not PullAlways")
	}
	if !build.Spec.Strategy.CustomStrategy.ForcePull {
		t.Errorf("ForcePull was not set on resulting build")
	}
}

func TestBuildOverrideLabels(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	labels := []buildapi.ImageLabel{{Name: "KEY", Value: "VALUE"}}
	oclient, kclientset := setupBuildOverridesAdmissionTest(t, &overridesapi.BuildOverridesConfig{
		ImageLabels: labels,
	})
	build, _ := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := build.Spec.Output.ImageLabels; !reflect.DeepEqual(labels, actual) {
		t.Errorf("Resulting build did not get expected labels: %v", actual)
	}
}

func TestBuildOverrideNodeSelectors(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	selectors := map[string]string{"KEY": "VALUE"}
	oclient, kclientset := setupBuildOverridesAdmissionTest(t, &overridesapi.BuildOverridesConfig{
		NodeSelector: selectors,
	})
	_, pod := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := pod.Spec.NodeSelector; !reflect.DeepEqual(selectors, actual) {
		t.Errorf("Resulting build did not get expected nodeselectors: %v", actual)
	}
}

func TestBuildOverrideAnnotations(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	annotations := map[string]string{"KEY": "VALUE"}
	oclient, kclientset := setupBuildOverridesAdmissionTest(t, &overridesapi.BuildOverridesConfig{
		Annotations: annotations,
	})
	_, pod := runBuildPodAdmissionTest(t, oclient, kclientset, buildPodAdmissionTestDockerBuild())
	if actual := pod.Annotations; strings.Compare(actual["KEY"], annotations["KEY"]) != 0 {
		t.Errorf("Resulting build did not get expected annotations: %v", actual)
	}
}

func buildPodAdmissionTestCustomBuild() *buildapi.Build {
	build := &buildapi.Build{ObjectMeta: kapi.ObjectMeta{
		Labels: map[string]string{
			buildapi.BuildConfigLabel:    "mock-build-config",
			buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
		},
	}}
	build.Name = "test-custom-build"
	build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "http://test/src"}
	build.Spec.Strategy.CustomStrategy = &buildapi.CustomBuildStrategy{}
	build.Spec.Strategy.CustomStrategy.From.Kind = "DockerImage"
	build.Spec.Strategy.CustomStrategy.From.Name = "test/image"
	return build
}

func buildPodAdmissionTestDockerBuild() *buildapi.Build {
	build := &buildapi.Build{ObjectMeta: kapi.ObjectMeta{
		Labels: map[string]string{
			buildapi.BuildConfigLabel:    "mock-build-config",
			buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
		},
	}}
	build.Name = "test-build"
	build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "http://test/src"}
	build.Spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{}
	return build
}

func runBuildPodAdmissionTest(t *testing.T, client *client.Client, kclientset *kclientset.Clientset, build *buildapi.Build) (*buildapi.Build, *kapi.Pod) {

	ns := testutil.Namespace()
	_, err := client.Builds(ns).Create(build)
	if err != nil {
		t.Fatalf("%v", err)
	}

	watchOpt := kapi.ListOptions{
		FieldSelector: fields.OneTermEqualSelector(
			"metadata.name",
			buildapi.GetBuildPodName(build),
		),
	}
	podWatch, err := kclientset.Core().Pods(ns).Watch(watchOpt)
	if err != nil {
		t.Fatalf("%v", err)
	}
	type resultObjs struct {
		build *buildapi.Build
		pod   *kapi.Pod
	}
	result := make(chan resultObjs)
	defer podWatch.Stop()
	go func() {
		for e := range podWatch.ResultChan() {
			if e.Type == watchapi.Added {
				pod, ok := e.Object.(*kapi.Pod)
				if !ok {
					t.Fatalf("unexpected object: %v", e.Object)
				}
				build := (*buildtestutil.TestPod)(pod).GetBuild(t)
				result <- resultObjs{build: build, pod: pod}
			}
		}
	}()

	select {
	case <-time.After(buildPodAdmissionTestTimeout):
		t.Fatalf("timed out after %v", buildPodAdmissionTestTimeout)
	case objs := <-result:
		return objs.build, objs.pod
	}
	return nil, nil
}

func setupBuildDefaultsAdmissionTest(t *testing.T, defaultsConfig *defaultsapi.BuildDefaultsConfig) (*client.Client, *kclientset.Clientset) {
	return setupBuildPodAdmissionTest(t, map[string]configapi.AdmissionPluginConfig{
		"BuildDefaults": {
			Configuration: defaultsConfig,
		},
	})
}

func setupBuildOverridesAdmissionTest(t *testing.T, overridesConfig *overridesapi.BuildOverridesConfig) (*client.Client, *kclientset.Clientset) {
	return setupBuildPodAdmissionTest(t, map[string]configapi.AdmissionPluginConfig{
		"BuildOverrides": {
			Configuration: overridesConfig,
		},
	})
}

func setupBuildPodAdmissionTest(t *testing.T, pluginConfig map[string]configapi.AdmissionPluginConfig) (*client.Client, *kclientset.Clientset) {
	testutil.RequireEtcd(t)
	master, err := testserver.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("%v", err)
	}
	master.AdmissionConfig.PluginConfig = pluginConfig
	clusterAdminKubeConfig, err := testserver.StartConfiguredMaster(master)
	if err != nil {
		t.Fatalf("%v", err)
	}
	clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("%v", err)
	}

	clusterAdminKubeClientset, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("%v", err)
	}

	_, err = clusterAdminKubeClientset.Namespaces().Create(&kapi.Namespace{
		ObjectMeta: kapi.ObjectMeta{Name: testutil.Namespace()},
	})
	if err != nil {
		t.Fatalf("%v", err)
	}

	err = testserver.WaitForServiceAccounts(
		clusterAdminKubeClientset,
		testutil.Namespace(),
		[]string{
			bootstrappolicy.BuilderServiceAccountName,
			bootstrappolicy.DefaultServiceAccountName,
		})
	if err != nil {
		t.Fatalf("%v", err)
	}

	return clusterAdminClient, clusterAdminKubeClientset
}