package util

import (
	"reflect"
	"sort"
	"strconv"
	"testing"
	"time"

	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/unversioned"

	deployapi "github.com/openshift/origin/pkg/deploy/api"
	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
	deployv1 "github.com/openshift/origin/pkg/deploy/api/v1"

	_ "github.com/openshift/origin/pkg/api/install"
)

func podTemplateA() *kapi.PodTemplateSpec {
	t := deploytest.OkPodTemplate()
	t.Spec.Containers = append(t.Spec.Containers, kapi.Container{
		Name:  "container1",
		Image: "registry:8080/repo1:ref1",
	})
	return t
}

func podTemplateB() *kapi.PodTemplateSpec {
	t := podTemplateA()
	t.Labels = map[string]string{"c": "d"}
	return t
}

func podTemplateC() *kapi.PodTemplateSpec {
	t := podTemplateA()
	t.Spec.Containers[0] = kapi.Container{
		Name:  "container2",
		Image: "registry:8080/repo1:ref3",
	}

	return t
}

func podTemplateD() *kapi.PodTemplateSpec {
	t := podTemplateA()
	t.Spec.Containers = append(t.Spec.Containers, kapi.Container{
		Name:  "container2",
		Image: "registry:8080/repo1:ref4",
	})

	return t
}

func TestPodName(t *testing.T) {
	deployment := &kapi.ReplicationController{
		ObjectMeta: kapi.ObjectMeta{
			Name: "testName",
		},
	}
	expected := "testName-deploy"
	actual := DeployerPodNameForDeployment(deployment.Name)
	if expected != actual {
		t.Errorf("Unexpected pod name for deployment. Expected: %s Got: %s", expected, actual)
	}
}

func TestMakeDeploymentOk(t *testing.T) {
	config := deploytest.OkDeploymentConfig(1)
	deployment, err := MakeDeployment(config, kapi.Codecs.LegacyCodec(deployv1.SchemeGroupVersion))

	if err != nil {
		t.Fatalf("unexpected error: %#v", err)
	}

	expectedAnnotations := map[string]string{
		deployapi.DeploymentConfigAnnotation:  config.Name,
		deployapi.DeploymentStatusAnnotation:  string(deployapi.DeploymentStatusNew),
		deployapi.DeploymentVersionAnnotation: strconv.FormatInt(config.Status.LatestVersion, 10),
	}

	for key, expected := range expectedAnnotations {
		if actual := deployment.Annotations[key]; actual != expected {
			t.Fatalf("expected deployment annotation %s=%s, got %s", key, expected, actual)
		}
	}

	expectedAnnotations = map[string]string{
		deployapi.DeploymentAnnotation:        deployment.Name,
		deployapi.DeploymentConfigAnnotation:  config.Name,
		deployapi.DeploymentVersionAnnotation: strconv.FormatInt(config.Status.LatestVersion, 10),
	}

	for key, expected := range expectedAnnotations {
		if actual := deployment.Spec.Template.Annotations[key]; actual != expected {
			t.Fatalf("expected pod template annotation %s=%s, got %s", key, expected, actual)
		}
	}

	if len(EncodedDeploymentConfigFor(deployment)) == 0 {
		t.Fatalf("expected deployment with DeploymentEncodedConfigAnnotation annotation")
	}

	if decodedConfig, err := DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployv1.SchemeGroupVersion)); err != nil {
		t.Fatalf("invalid encoded config on deployment: %v", err)
	} else {
		if e, a := config.Name, decodedConfig.Name; e != a {
			t.Fatalf("encoded config name doesn't match source config")
		}
		// TODO: more assertions
	}

	if deployment.Spec.Replicas != 0 {
		t.Fatalf("expected deployment replicas to be 0")
	}

	if l, e, a := deployapi.DeploymentConfigAnnotation, config.Name, deployment.Labels[deployapi.DeploymentConfigAnnotation]; e != a {
		t.Fatalf("expected label %s=%s, got %s", l, e, a)
	}

	if e, a := config.Name, deployment.Spec.Template.Labels[deployapi.DeploymentConfigLabel]; e != a {
		t.Fatalf("expected label DeploymentConfigLabel=%s, got %s", e, a)
	}

	if e, a := deployment.Name, deployment.Spec.Template.Labels[deployapi.DeploymentLabel]; e != a {
		t.Fatalf("expected label DeploymentLabel=%s, got %s", e, a)
	}

	if e, a := config.Name, deployment.Spec.Selector[deployapi.DeploymentConfigLabel]; e != a {
		t.Fatalf("expected selector DeploymentConfigLabel=%s, got %s", e, a)
	}

	if e, a := deployment.Name, deployment.Spec.Selector[deployapi.DeploymentLabel]; e != a {
		t.Fatalf("expected selector DeploymentLabel=%s, got %s", e, a)
	}
}

func TestDeploymentsByLatestVersion_sorting(t *testing.T) {
	mkdeployment := func(version int64) kapi.ReplicationController {
		deployment, _ := MakeDeployment(deploytest.OkDeploymentConfig(version), kapi.Codecs.LegacyCodec(deployv1.SchemeGroupVersion))
		return *deployment
	}
	deployments := []kapi.ReplicationController{
		mkdeployment(4),
		mkdeployment(1),
		mkdeployment(2),
		mkdeployment(3),
	}
	sort.Sort(ByLatestVersionAsc(deployments))
	for i := int64(0); i < 4; i++ {
		if e, a := i+1, DeploymentVersionFor(&deployments[i]); e != a {
			t.Errorf("expected deployment[%d]=%d, got %d", i, e, a)
		}
	}
	sort.Sort(ByLatestVersionDesc(deployments))
	for i := int64(0); i < 4; i++ {
		if e, a := 4-i, DeploymentVersionFor(&deployments[i]); e != a {
			t.Errorf("expected deployment[%d]=%d, got %d", i, e, a)
		}
	}
}

// TestSort verifies that builds are sorted by most recently created
func TestSort(t *testing.T) {
	present := unversioned.Now()
	past := unversioned.NewTime(present.Time.Add(-1 * time.Minute))
	controllers := []*kapi.ReplicationController{
		{
			ObjectMeta: kapi.ObjectMeta{
				Name:              "past",
				CreationTimestamp: past,
			},
		},
		{
			ObjectMeta: kapi.ObjectMeta{
				Name:              "present",
				CreationTimestamp: present,
			},
		},
	}
	sort.Sort(ByMostRecent(controllers))
	if controllers[0].Name != "present" {
		t.Errorf("Unexpected sort order")
	}
	if controllers[1].Name != "past" {
		t.Errorf("Unexpected sort order")
	}
}

func TestCanTransitionPhase(t *testing.T) {
	tests := []struct {
		name          string
		current, next deployapi.DeploymentStatus
		expected      bool
	}{
		{
			name:     "New->New",
			current:  deployapi.DeploymentStatusNew,
			next:     deployapi.DeploymentStatusNew,
			expected: false,
		},
		{
			name:     "New->Pending",
			current:  deployapi.DeploymentStatusNew,
			next:     deployapi.DeploymentStatusPending,
			expected: true,
		},
		{
			name:     "New->Running",
			current:  deployapi.DeploymentStatusNew,
			next:     deployapi.DeploymentStatusRunning,
			expected: true,
		},
		{
			name:     "New->Complete",
			current:  deployapi.DeploymentStatusNew,
			next:     deployapi.DeploymentStatusComplete,
			expected: true,
		},
		{
			name:     "New->Failed",
			current:  deployapi.DeploymentStatusNew,
			next:     deployapi.DeploymentStatusFailed,
			expected: true,
		},
		{
			name:     "Pending->New",
			current:  deployapi.DeploymentStatusPending,
			next:     deployapi.DeploymentStatusNew,
			expected: false,
		},
		{
			name:     "Pending->Pending",
			current:  deployapi.DeploymentStatusPending,
			next:     deployapi.DeploymentStatusPending,
			expected: false,
		},
		{
			name:     "Pending->Running",
			current:  deployapi.DeploymentStatusPending,
			next:     deployapi.DeploymentStatusRunning,
			expected: true,
		},
		{
			name:     "Pending->Failed",
			current:  deployapi.DeploymentStatusPending,
			next:     deployapi.DeploymentStatusFailed,
			expected: true,
		},
		{
			name:     "Pending->Complete",
			current:  deployapi.DeploymentStatusPending,
			next:     deployapi.DeploymentStatusComplete,
			expected: true,
		},
		{
			name:     "Running->New",
			current:  deployapi.DeploymentStatusRunning,
			next:     deployapi.DeploymentStatusNew,
			expected: false,
		},
		{
			name:     "Running->Pending",
			current:  deployapi.DeploymentStatusRunning,
			next:     deployapi.DeploymentStatusPending,
			expected: false,
		},
		{
			name:     "Running->Running",
			current:  deployapi.DeploymentStatusRunning,
			next:     deployapi.DeploymentStatusRunning,
			expected: false,
		},
		{
			name:     "Running->Failed",
			current:  deployapi.DeploymentStatusRunning,
			next:     deployapi.DeploymentStatusFailed,
			expected: true,
		},
		{
			name:     "Running->Complete",
			current:  deployapi.DeploymentStatusRunning,
			next:     deployapi.DeploymentStatusComplete,
			expected: true,
		},
		{
			name:     "Complete->New",
			current:  deployapi.DeploymentStatusComplete,
			next:     deployapi.DeploymentStatusNew,
			expected: false,
		},
		{
			name:     "Complete->Pending",
			current:  deployapi.DeploymentStatusComplete,
			next:     deployapi.DeploymentStatusPending,
			expected: false,
		},
		{
			name:     "Complete->Running",
			current:  deployapi.DeploymentStatusComplete,
			next:     deployapi.DeploymentStatusRunning,
			expected: false,
		},
		{
			name:     "Complete->Failed",
			current:  deployapi.DeploymentStatusComplete,
			next:     deployapi.DeploymentStatusFailed,
			expected: false,
		},
		{
			name:     "Complete->Complete",
			current:  deployapi.DeploymentStatusComplete,
			next:     deployapi.DeploymentStatusComplete,
			expected: false,
		},
		{
			name:     "Failed->New",
			current:  deployapi.DeploymentStatusFailed,
			next:     deployapi.DeploymentStatusNew,
			expected: false,
		},
		{
			name:     "Failed->Pending",
			current:  deployapi.DeploymentStatusFailed,
			next:     deployapi.DeploymentStatusPending,
			expected: false,
		},
		{
			name:     "Failed->Running",
			current:  deployapi.DeploymentStatusFailed,
			next:     deployapi.DeploymentStatusRunning,
			expected: false,
		},
		{
			name:     "Failed->Complete",
			current:  deployapi.DeploymentStatusFailed,
			next:     deployapi.DeploymentStatusComplete,
			expected: false,
		},
		{
			name:     "Failed->Failed",
			current:  deployapi.DeploymentStatusFailed,
			next:     deployapi.DeploymentStatusFailed,
			expected: false,
		},
	}

	for _, test := range tests {
		got := CanTransitionPhase(test.current, test.next)
		if got != test.expected {
			t.Errorf("%s: expected %t, got %t", test.name, test.expected, got)
		}
	}
}

var (
	now     = unversioned.Now()
	later   = unversioned.Time{Time: now.Add(time.Minute)}
	earlier = unversioned.Time{Time: now.Add(-time.Minute)}

	condProgressing = func() deployapi.DeploymentCondition {
		return deployapi.DeploymentCondition{
			Type:               deployapi.DeploymentProgressing,
			Status:             kapi.ConditionTrue,
			LastTransitionTime: now,
			Reason:             "ForSomeReason",
		}
	}

	condProgressingDifferentTime = func() deployapi.DeploymentCondition {
		return deployapi.DeploymentCondition{
			Type:               deployapi.DeploymentProgressing,
			Status:             kapi.ConditionTrue,
			LastTransitionTime: later,
			Reason:             "ForSomeReason",
		}
	}

	condProgressingDifferentReason = func() deployapi.DeploymentCondition {
		return deployapi.DeploymentCondition{
			Type:               deployapi.DeploymentProgressing,
			Status:             kapi.ConditionTrue,
			LastTransitionTime: later,
			Reason:             "BecauseItIs",
		}
	}

	condNotProgressing = func() deployapi.DeploymentCondition {
		return deployapi.DeploymentCondition{
			Type:               deployapi.DeploymentProgressing,
			Status:             kapi.ConditionFalse,
			LastUpdateTime:     earlier,
			LastTransitionTime: earlier,
			Reason:             "NotYet",
		}
	}

	condAvailable = func() deployapi.DeploymentCondition {
		return deployapi.DeploymentCondition{
			Type:   deployapi.DeploymentAvailable,
			Status: kapi.ConditionTrue,
			Reason: "AwesomeController",
		}
	}
)

func TestGetCondition(t *testing.T) {
	exampleStatus := func() deployapi.DeploymentConfigStatus {
		return deployapi.DeploymentConfigStatus{
			Conditions: []deployapi.DeploymentCondition{condProgressing(), condAvailable()},
		}
	}

	tests := []struct {
		name string

		status     deployapi.DeploymentConfigStatus
		condType   deployapi.DeploymentConditionType
		condStatus kapi.ConditionStatus
		condReason string

		expected bool
	}{
		{
			name: "condition exists",

			status:   exampleStatus(),
			condType: deployapi.DeploymentAvailable,

			expected: true,
		},
		{
			name: "condition does not exist",

			status:   exampleStatus(),
			condType: deployapi.DeploymentReplicaFailure,

			expected: false,
		},
	}

	for _, test := range tests {
		cond := GetDeploymentCondition(test.status, test.condType)
		exists := cond != nil
		if exists != test.expected {
			t.Errorf("%s: expected condition to exist: %t, got: %t", test.name, test.expected, exists)
		}
	}
}

func TestSetCondition(t *testing.T) {
	tests := []struct {
		name string

		status *deployapi.DeploymentConfigStatus
		cond   deployapi.DeploymentCondition

		expectedStatus *deployapi.DeploymentConfigStatus
	}{
		{
			name: "set for the first time",

			status: &deployapi.DeploymentConfigStatus{},
			cond:   condAvailable(),

			expectedStatus: &deployapi.DeploymentConfigStatus{
				Conditions: []deployapi.DeploymentCondition{
					condAvailable(),
				},
			},
		},
		{
			name: "simple set",

			status: &deployapi.DeploymentConfigStatus{
				Conditions: []deployapi.DeploymentCondition{
					condProgressing(),
				},
			},
			cond: condAvailable(),

			expectedStatus: &deployapi.DeploymentConfigStatus{
				Conditions: []deployapi.DeploymentCondition{
					condProgressing(), condAvailable(),
				},
			},
		},
		{
			name: "replace if status changes",

			status: &deployapi.DeploymentConfigStatus{
				Conditions: []deployapi.DeploymentCondition{
					condNotProgressing(),
				},
			},
			cond: condProgressing(),

			expectedStatus: &deployapi.DeploymentConfigStatus{Conditions: []deployapi.DeploymentCondition{condProgressing()}},
		},
		{
			name: "replace if reason changes",

			status: &deployapi.DeploymentConfigStatus{
				Conditions: []deployapi.DeploymentCondition{
					condProgressing(),
				},
			},
			cond: condProgressingDifferentReason(),

			expectedStatus: &deployapi.DeploymentConfigStatus{
				Conditions: []deployapi.DeploymentCondition{
					{
						Type:   deployapi.DeploymentProgressing,
						Status: kapi.ConditionTrue,
						// Note that LastTransitionTime stays the same.
						LastTransitionTime: now,
						// Only the reason changes.
						Reason: "BecauseItIs",
					},
				},
			},
		},
		{
			name: "don't replace if status and reason don't change",

			status: &deployapi.DeploymentConfigStatus{
				Conditions: []deployapi.DeploymentCondition{
					condProgressing(),
				},
			},
			cond: condProgressingDifferentTime(),

			expectedStatus: &deployapi.DeploymentConfigStatus{Conditions: []deployapi.DeploymentCondition{condProgressing()}},
		},
	}

	for _, test := range tests {
		t.Logf("running test %q", test.name)
		SetDeploymentCondition(test.status, test.cond)
		if !reflect.DeepEqual(test.status, test.expectedStatus) {
			t.Errorf("expected status: %v, got: %v", test.expectedStatus, test.status)
		}
	}
}

func TestRemoveCondition(t *testing.T) {
	exampleStatus := func() *deployapi.DeploymentConfigStatus {
		return &deployapi.DeploymentConfigStatus{
			Conditions: []deployapi.DeploymentCondition{condProgressing(), condAvailable()},
		}
	}

	tests := []struct {
		name string

		status   *deployapi.DeploymentConfigStatus
		condType deployapi.DeploymentConditionType

		expectedStatus *deployapi.DeploymentConfigStatus
	}{
		{
			name: "remove from empty status",

			status:   &deployapi.DeploymentConfigStatus{},
			condType: deployapi.DeploymentProgressing,

			expectedStatus: &deployapi.DeploymentConfigStatus{},
		},
		{
			name: "simple remove",

			status:   &deployapi.DeploymentConfigStatus{Conditions: []deployapi.DeploymentCondition{condProgressing()}},
			condType: deployapi.DeploymentProgressing,

			expectedStatus: &deployapi.DeploymentConfigStatus{},
		},
		{
			name: "doesn't remove anything",

			status:   exampleStatus(),
			condType: deployapi.DeploymentReplicaFailure,

			expectedStatus: exampleStatus(),
		},
	}

	for _, test := range tests {
		RemoveDeploymentCondition(test.status, test.condType)
		if !reflect.DeepEqual(test.status, test.expectedStatus) {
			t.Errorf("%s: expected status: %v, got: %v", test.name, test.expectedStatus, test.status)
		}
	}
}