package controller

import (
	"testing"

	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"

	deployapi "github.com/openshift/origin/pkg/deploy/api"
	deployapitest "github.com/openshift/origin/pkg/deploy/api/test"
	deploytest "github.com/openshift/origin/pkg/deploy/controller/test"
	imageapi "github.com/openshift/origin/pkg/image/api"
)

type testIcDeploymentConfigInterface struct {
	UpdateDeploymentConfigFunc   func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
	GenerateDeploymentConfigFunc func(namespace, name string) (*deployapi.DeploymentConfig, error)
}

func (i *testIcDeploymentConfigInterface) UpdateDeploymentConfig(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
	return i.UpdateDeploymentConfigFunc(namespace, config)
}
func (i *testIcDeploymentConfigInterface) GenerateDeploymentConfig(namespace, name string) (*deployapi.DeploymentConfig, error) {
	return i.GenerateDeploymentConfigFunc(namespace, name)
}

const (
	nonDefaultNamespace = "nondefaultnamespace"
)

func TestUnregisteredContainer(t *testing.T) {
	config := deployapitest.OkDeploymentConfig(1)
	config.Triggers[0].ImageChangeParams.ContainerNames = []string{"container-3"}

	controller := &ImageChangeController{
		DeploymentConfigInterface: &testIcDeploymentConfigInterface{
			UpdateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
				t.Fatalf("unexpected deployment config update")
				return nil, nil
			},
			GenerateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
				t.Fatalf("unexpected generator call")
				return nil, nil
			},
		},
		NextImageRepository: func() *imageapi.ImageRepository {
			return tagUpdate()
		},
		DeploymentConfigStore: deploytest.NewFakeDeploymentConfigStore(config),
	}

	// verify no-op
	controller.HandleImageRepo()
}

func TestImageChangeForNonAutomaticTag(t *testing.T) {
	config := deployapitest.OkDeploymentConfig(1)
	config.Triggers[0].ImageChangeParams.Automatic = false

	controller := &ImageChangeController{
		DeploymentConfigInterface: &testIcDeploymentConfigInterface{
			UpdateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
				t.Fatalf("unexpected deployment config update")
				return nil, nil
			},
			GenerateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
				t.Fatalf("unexpected generator call")
				return nil, nil
			},
		},
		NextImageRepository: func() *imageapi.ImageRepository {
			return tagUpdate()
		},
		DeploymentConfigStore: deploytest.NewFakeDeploymentConfigStore(config),
	}

	// verify no-op
	controller.HandleImageRepo()
}

func TestImageChangeForUnregisteredTag(t *testing.T) {
	config := imageChangeDeploymentConfig()

	controller := &ImageChangeController{
		DeploymentConfigInterface: &testIcDeploymentConfigInterface{
			UpdateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
				t.Fatalf("unexpected deployment config update")
				return nil, nil
			},
			GenerateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
				t.Fatalf("unexpected generator call")
				return nil, nil
			},
		},
		NextImageRepository: func() *imageapi.ImageRepository {
			imageRepo := tagUpdate()
			imageRepo.Tags = map[string]string{
				"unknown-tag": "ref-1",
			}
			return imageRepo
		},
		DeploymentConfigStore: deploytest.NewFakeDeploymentConfigStore(config),
	}

	// verify no-op
	controller.HandleImageRepo()
}

func TestImageChange(t *testing.T) {
	var (
		generatedConfig          *deployapi.DeploymentConfig
		updatedConfig            *deployapi.DeploymentConfig
		generatedConfigNamespace string
		updatedConfigNamespace   string
	)

	controller := &ImageChangeController{
		DeploymentConfigInterface: &testIcDeploymentConfigInterface{
			UpdateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
				updatedConfigNamespace = namespace
				updatedConfig = config
				return updatedConfig, nil
			},
			GenerateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) {
				generatedConfigNamespace = namespace
				generatedConfig = regeneratedConfig(namespace)
				return generatedConfig, nil
			},
		},
		NextImageRepository: func() *imageapi.ImageRepository {
			return tagUpdate()
		},
		DeploymentConfigStore: deploytest.NewFakeDeploymentConfigStore(imageChangeDeploymentConfig()),
	}

	controller.HandleImageRepo()

	if generatedConfig == nil {
		t.Fatalf("expected config generation to occur")
	}

	if updatedConfig == nil {
		t.Fatalf("expected an updated deployment config")
	} else if updatedConfig.Details == nil {
		t.Fatalf("expected config change details to be set")
	} else if updatedConfig.Details.Causes == nil {
		t.Fatalf("expected config change causes to be set")
	} else if updatedConfig.Details.Causes[0].Type != deployapi.DeploymentTriggerOnImageChange {
		t.Fatalf("expected ChangeLog details to be set to image change trigger, got %s", updatedConfig.Details.Causes[0].Type)
	}
	if generatedConfigNamespace != nonDefaultNamespace {
		t.Errorf("Expected generatedConfigNamespace %v, got %v", nonDefaultNamespace, generatedConfigNamespace)
	}
	if updatedConfigNamespace != nonDefaultNamespace {
		t.Errorf("Expected updatedConfigNamespace %v, got %v", nonDefaultNamespace, updatedConfigNamespace)
	}

	if e, a := updatedConfig.Name, generatedConfig.Name; e != a {
		t.Fatalf("expected updated config with id %s, got %s", e, a)
	}

	if e, a := updatedConfig.Name, generatedConfig.Name; e != a {
		t.Fatalf("expected updated config with id %s, got %s", e, a)
	}
}

// Utilities and convenience methods

func originalImageRepo() *imageapi.ImageRepository {
	return &imageapi.ImageRepository{
		ObjectMeta:            kapi.ObjectMeta{Name: "test-image-repo", Namespace: nonDefaultNamespace},
		DockerImageRepository: "registry:8080/openshift/test-image",
		Tags: map[string]string{
			"test-tag": "ref-1",
		},
	}
}

func unregisteredTagUpdate() *imageapi.ImageRepository {
	return &imageapi.ImageRepository{
		ObjectMeta:            kapi.ObjectMeta{Name: "test-image-repo", Namespace: nonDefaultNamespace},
		DockerImageRepository: "registry:8080/openshift/test-image",
		Tags: map[string]string{
			"test-tag":       "ref-1",
			"other-test-tag": "ref-x",
		},
	}
}

func tagUpdate() *imageapi.ImageRepository {
	return &imageapi.ImageRepository{
		ObjectMeta:            kapi.ObjectMeta{Name: "test-image-repo", Namespace: nonDefaultNamespace},
		DockerImageRepository: "registry:8080/openshift/test-image",
		Tags: map[string]string{
			"test-tag": "ref-2",
		},
	}
}

func imageChangeDeploymentConfig() *deployapi.DeploymentConfig {
	return &deployapi.DeploymentConfig{
		ObjectMeta: kapi.ObjectMeta{Name: "image-change-deploy-config"},
		Triggers: []deployapi.DeploymentTriggerPolicy{
			{
				Type: deployapi.DeploymentTriggerOnImageChange,
				ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{
					Automatic:      true,
					ContainerNames: []string{"container-1"},
					RepositoryName: "registry:8080/openshift/test-image",
					Tag:            "test-tag",
				},
			},
		},
		Template: deployapi.DeploymentTemplate{
			Strategy: deployapi.DeploymentStrategy{
				Type: deployapi.DeploymentStrategyTypeRecreate,
			},
			ControllerTemplate: kapi.ReplicationControllerSpec{
				Replicas: 1,
				Selector: map[string]string{
					"name": "test-pod",
				},
				Template: &kapi.PodTemplateSpec{
					ObjectMeta: kapi.ObjectMeta{
						Labels: map[string]string{
							"name": "test-pod",
						},
					},
					Spec: kapi.PodSpec{
						Containers: []kapi.Container{
							{
								Name:  "container-1",
								Image: "registry:8080/openshift/test-image:ref-1",
							},
						},
					},
				},
			},
		},
	}
}

func regeneratedConfig(namespace string) *deployapi.DeploymentConfig {
	return &deployapi.DeploymentConfig{
		ObjectMeta: kapi.ObjectMeta{Name: "image-change-deploy-config", Namespace: namespace},
		Triggers: []deployapi.DeploymentTriggerPolicy{
			{
				Type: deployapi.DeploymentTriggerOnImageChange,
				ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{
					Automatic:      true,
					ContainerNames: []string{"container-1"},
					RepositoryName: "registry:8080/openshift/test-image",
					Tag:            "test-tag",
				},
			},
		},
		Template: deployapi.DeploymentTemplate{
			Strategy: deployapi.DeploymentStrategy{
				Type: deployapi.DeploymentStrategyTypeRecreate,
			},
			ControllerTemplate: kapi.ReplicationControllerSpec{
				Replicas: 1,
				Selector: map[string]string{
					"name": "test-pod",
				},
				Template: &kapi.PodTemplateSpec{
					ObjectMeta: kapi.ObjectMeta{
						Labels: map[string]string{
							"name": "test-pod",
						},
					},
					Spec: kapi.PodSpec{
						Containers: []kapi.Container{
							{
								Name:  "container-1",
								Image: "registry:8080/openshift/test-image:ref-2",
							},
						},
					},
				},
			},
		},
	}
}

func unregisteredConfig() *deployapi.DeploymentConfig {
	d := imageChangeDeploymentConfig()
	d.Triggers[0].ImageChangeParams.ContainerNames = []string{"container-3"}
	return d
}