package integration

import (
	"testing"

	kapi "k8s.io/kubernetes/pkg/api"
	apierrors "k8s.io/kubernetes/pkg/api/errors"
	kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	"k8s.io/kubernetes/pkg/client/restclient"
	"k8s.io/kubernetes/pkg/labels"

	"github.com/openshift/origin/pkg/client"
	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	requestlimit "github.com/openshift/origin/pkg/project/admission/requestlimit/api"
	projectapi "github.com/openshift/origin/pkg/project/api"
	userapi "github.com/openshift/origin/pkg/user/api"
	testutil "github.com/openshift/origin/test/util"
	testserver "github.com/openshift/origin/test/util/server"
)

func setupProjectRequestLimitTest(t *testing.T, pluginConfig *requestlimit.ProjectRequestLimitConfig) (kclientset.Interface, client.Interface, *restclient.Config) {
	testutil.RequireEtcd(t)
	masterConfig, err := testserver.DefaultMasterOptions()
	if err != nil {
		t.Fatalf("error creating config: %v", err)
	}
	masterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{
		"ProjectRequestLimit": {
			Configuration: pluginConfig,
		},
	}
	kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig)
	if err != nil {
		t.Fatalf("error starting server: %v", err)
	}
	kubeClient, err := testutil.GetClusterAdminKubeClient(kubeConfigFile)
	if err != nil {
		t.Fatalf("error getting client: %v", err)
	}
	openshiftClient, err := testutil.GetClusterAdminClient(kubeConfigFile)
	if err != nil {
		t.Fatalf("error getting openshift client: %v", err)
	}
	clientConfig, err := testutil.GetClusterAdminClientConfig(kubeConfigFile)
	if err != nil {
		t.Fatalf("error getting client config: %v", err)
	}
	return kubeClient, openshiftClient, clientConfig
}

func setupProjectRequestLimitUsers(t *testing.T, client client.Interface, users map[string]labels.Set) {
	for userName, labels := range users {
		user := &userapi.User{}
		user.Name = userName
		user.Labels = map[string]string(labels)
		_, err := client.Users().Create(user)
		if err != nil {
			t.Fatalf("Could not create user %s: %v", userName, err)
		}
	}
}

func setupProjectRequestLimitNamespaces(t *testing.T, kclient kclientset.Interface, namespacesByRequester map[string]int) {
	for requester, nsCount := range namespacesByRequester {
		for i := 0; i < nsCount; i++ {
			ns := &kapi.Namespace{}
			ns.GenerateName = "testns"
			ns.Annotations = map[string]string{projectapi.ProjectRequester: requester}
			_, err := kclient.Core().Namespaces().Create(ns)
			if err != nil {
				t.Fatalf("Could not create namespace for requester %s: %v", requester, err)
			}
		}
	}
}

func intPointer(n int) *int {
	return &n
}

func projectRequestLimitEmptyConfig() *requestlimit.ProjectRequestLimitConfig {
	return &requestlimit.ProjectRequestLimitConfig{}
}

func projectRequestLimitMultiLevelConfig() *requestlimit.ProjectRequestLimitConfig {
	return &requestlimit.ProjectRequestLimitConfig{
		Limits: []requestlimit.ProjectLimitBySelector{
			{
				Selector:    map[string]string{"level": "gold"},
				MaxProjects: intPointer(3),
			},
			{
				Selector:    map[string]string{"level": "silver"},
				MaxProjects: intPointer(2),
			},
			{
				Selector:    nil,
				MaxProjects: intPointer(1),
			},
		},
	}
}

func projectRequestLimitSingleDefaultConfig() *requestlimit.ProjectRequestLimitConfig {
	return &requestlimit.ProjectRequestLimitConfig{
		Limits: []requestlimit.ProjectLimitBySelector{
			{
				Selector:    nil,
				MaxProjects: intPointer(1),
			},
		},

		MaxProjectsForSystemUsers: intPointer(1),
	}
}

func projectRequestLimitUsers() map[string]labels.Set {
	return map[string]labels.Set{
		"regular": {"level": "none"},
		"gold":    {"level": "gold"},
		"silver":  {"level": "silver"},
	}
}

func TestProjectRequestLimitMultiLevelConfig(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	kclient, oclient, clientConfig := setupProjectRequestLimitTest(t, projectRequestLimitMultiLevelConfig())
	setupProjectRequestLimitUsers(t, oclient, projectRequestLimitUsers())
	setupProjectRequestLimitNamespaces(t, kclient, map[string]int{
		"regular": 0,
		"silver":  2,
		"gold":    2,
	})
	testProjectRequestLimitAdmission(t, "multi-level config", clientConfig, map[string]bool{
		"regular": true,
		"silver":  false,
		"gold":    true,
	})
}

func TestProjectRequestLimitEmptyConfig(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	kclient, oclient, clientConfig := setupProjectRequestLimitTest(t, projectRequestLimitEmptyConfig())
	setupProjectRequestLimitUsers(t, oclient, projectRequestLimitUsers())
	setupProjectRequestLimitNamespaces(t, kclient, map[string]int{
		"regular": 5,
		"silver":  2,
		"gold":    2,
	})
	testProjectRequestLimitAdmission(t, "empty config", clientConfig, map[string]bool{
		"regular": true,
		"silver":  true,
		"gold":    true,
	})
}

func TestProjectRequestLimitSingleConfig(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	kclient, oclient, clientConfig := setupProjectRequestLimitTest(t, projectRequestLimitSingleDefaultConfig())
	setupProjectRequestLimitUsers(t, oclient, projectRequestLimitUsers())
	setupProjectRequestLimitNamespaces(t, kclient, map[string]int{
		"regular": 0,
		"silver":  1,
		"gold":    0,
	})
	testProjectRequestLimitAdmission(t, "single default config", clientConfig, map[string]bool{
		"regular": true,
		"silver":  false,
		"gold":    true,
	})
}

// we had a bug where this failed on ` uenxpected error: metadata.name: Invalid value: "system:admin": may not contain ":"`
// make sure we never have that bug again and that project limits for them work
func TestProjectRequestLimitAsSystemAdmin(t *testing.T) {
	defer testutil.DumpEtcdOnFailure(t)
	_, oclient, _ := setupProjectRequestLimitTest(t, projectRequestLimitSingleDefaultConfig())

	if _, err := oclient.ProjectRequests().Create(&projectapi.ProjectRequest{
		ObjectMeta: kapi.ObjectMeta{Name: "foo"},
	}); err != nil {
		t.Errorf("uenxpected error: %v", err)
	}
	if _, err := oclient.ProjectRequests().Create(&projectapi.ProjectRequest{
		ObjectMeta: kapi.ObjectMeta{Name: "bar"},
	}); !apierrors.IsForbidden(err) {
		t.Errorf("missing error: %v", err)
	}
}

func testProjectRequestLimitAdmission(t *testing.T, errorPrefix string, clientConfig *restclient.Config, tests map[string]bool) {
	for user, expectSuccess := range tests {
		oclient, _, _, err := testutil.GetClientForUser(*clientConfig, user)
		if err != nil {
			t.Fatalf("Error getting client for user %s: %v", user, err)
		}
		projectRequest := &projectapi.ProjectRequest{}
		projectRequest.Name = kapi.SimpleNameGenerator.GenerateName("test-projectreq")
		_, err = oclient.ProjectRequests().Create(projectRequest)
		if err != nil && expectSuccess {
			t.Errorf("%s: unexpected error for user %s: %v", errorPrefix, user, err)
			continue
		}
		if !expectSuccess {
			if err == nil {
				t.Errorf("%s: did not get expected error for user %s", errorPrefix, user)
				continue
			}
			if !apierrors.IsForbidden(err) {
				t.Errorf("%s: did not get an expected forbidden error for user %s. Got: %v", errorPrefix, user, err)
			}
		}
	}
}