/*
Copyright 2019 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package integration

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"k8s.io/client-go/dynamic"

	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	"k8s.io/apiextensions-apiserver/test/integration/fixtures"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
)

func TestLimits(t *testing.T) {
	tearDown, config, _, err := fixtures.StartDefaultServer(t)
	if err != nil {
		t.Fatal(err)
	}
	defer tearDown()

	apiExtensionClient, err := clientset.NewForConfig(config)
	if err != nil {
		t.Fatal(err)
	}
	dynamicClient, err := dynamic.NewForConfig(config)
	if err != nil {
		t.Fatal(err)
	}

	noxuDefinition := fixtures.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped)
	noxuDefinition, err = fixtures.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
	if err != nil {
		t.Fatal(err)
	}

	kind := noxuDefinition.Spec.Names.Kind
	apiVersion := noxuDefinition.Spec.Group + "/" + noxuDefinition.Spec.Version

	rest := apiExtensionClient.Discovery().RESTClient()

	// Create YAML over 3MB limit
	t.Run("create YAML over limit", func(t *testing.T) {
		yamlBody := []byte(fmt.Sprintf(`
apiVersion: %s
kind: %s
metadata:
  name: test
values: `+strings.Repeat("[", 3*1024*1024), apiVersion, kind))

		_, err := rest.Post().
			SetHeader("Accept", "application/yaml").
			SetHeader("Content-Type", "application/yaml").
			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
			Body(yamlBody).
			DoRaw(context.TODO())
		if !apierrors.IsRequestEntityTooLargeError(err) {
			t.Errorf("expected too large error, got %v", err)
		}
	})

	// Create YAML just under 3MB limit, nested
	t.Run("create YAML doc under limit, nested", func(t *testing.T) {
		if testing.Short() {
			t.Skip("skipping expensive test")
		}
		yamlBody := []byte(fmt.Sprintf(`
	apiVersion: %s
	kind: %s
	metadata:
	  name: test
	values: `+strings.Repeat("[", 3*1024*1024/2-500)+strings.Repeat("]", 3*1024*1024/2-500), apiVersion, kind))

		_, err := rest.Post().
			SetHeader("Accept", "application/yaml").
			SetHeader("Content-Type", "application/yaml").
			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
			Body(yamlBody).
			DoRaw(context.TODO())
		if !apierrors.IsBadRequest(err) {
			t.Errorf("expected bad request, got %v", err)
		}
	})

	// Create YAML just under 3MB limit, not nested
	t.Run("create YAML doc under limit, not nested", func(t *testing.T) {
		if testing.Short() {
			t.Skip("skipping expensive test")
		}
		yamlBody := []byte(fmt.Sprintf(`
		apiVersion: %s
		kind: %s
		metadata:
		  name: test
		values: `+strings.Repeat("[", 3*1024*1024-1000), apiVersion, kind))

		_, err := rest.Post().
			SetHeader("Accept", "application/yaml").
			SetHeader("Content-Type", "application/yaml").
			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
			Body(yamlBody).
			DoRaw(context.TODO())
		if !apierrors.IsBadRequest(err) {
			t.Errorf("expected bad request, got %v", err)
		}
	})

	// Create JSON over 3MB limit
	t.Run("create JSON over limit", func(t *testing.T) {
		jsonBody := []byte(fmt.Sprintf(`{
	"apiVersion": %q,
	"kind": %q,
	"metadata": {
	  "name": "test"
	},
	"values": `+strings.Repeat("[", 3*1024*1024/2)+strings.Repeat("]", 3*1024*1024/2)+"}", apiVersion, kind))

		_, err := rest.Post().
			SetHeader("Accept", "application/json").
			SetHeader("Content-Type", "application/json").
			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
			Body(jsonBody).
			DoRaw(context.TODO())
		if !apierrors.IsRequestEntityTooLargeError(err) {
			t.Errorf("expected too large error, got %v", err)
		}
	})

	// Create JSON just under 3MB limit, nested
	t.Run("create JSON doc under limit, nested", func(t *testing.T) {
		if testing.Short() {
			t.Skip("skipping expensive test")
		}
		jsonBody := []byte(fmt.Sprintf(`{
		"apiVersion": %q,
		"kind": %q,
		"metadata": {
		  "name": "test"
		},
		"values": `+strings.Repeat("[", 3*1024*1024/2-500)+strings.Repeat("]", 3*1024*1024/2-500)+"}", apiVersion, kind))

		_, err := rest.Post().
			SetHeader("Accept", "application/json").
			SetHeader("Content-Type", "application/json").
			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
			Body(jsonBody).
			DoRaw(context.TODO())
		if !apierrors.IsBadRequest(err) {
			t.Errorf("expected bad request, got %v", err)
		}
	})

	// Create JSON just under 3MB limit, not nested
	t.Run("create JSON doc under limit, not nested", func(t *testing.T) {
		if testing.Short() {
			t.Skip("skipping expensive test")
		}
		jsonBody := []byte(fmt.Sprintf(`{
			"apiVersion": %q,
			"kind": %q,
			"metadata": {
			  "name": "test"
			},
			"values": `+strings.Repeat("[", 3*1024*1024-1000)+"}", apiVersion, kind))

		_, err := rest.Post().
			SetHeader("Accept", "application/json").
			SetHeader("Content-Type", "application/json").
			AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
			Body(jsonBody).
			DoRaw(context.TODO())
		if !apierrors.IsBadRequest(err) {
			t.Errorf("expected bad request, got %v", err)
		}
	})

	// Create instance to allow patching
	{
		jsonBody := []byte(fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "metadata": {"name": "test"}}`, apiVersion, kind))
		_, err := rest.Post().AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).Body(jsonBody).DoRaw(context.TODO())
		if err != nil {
			t.Fatalf("error creating object: %v", err)
		}
	}

	t.Run("JSONPatchType nested patch under limit", func(t *testing.T) {
		if testing.Short() {
			t.Skip("skipping expensive test")
		}
		patchBody := []byte(`[{"op":"add","path":"/foo","value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}]`)
		err = rest.Patch(types.JSONPatchType).AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "test").
			Body(patchBody).Do(context.TODO()).Error()
		if !apierrors.IsBadRequest(err) {
			t.Errorf("expected success or bad request err, got %v", err)
		}
	})
	t.Run("MergePatchType nested patch under limit", func(t *testing.T) {
		if testing.Short() {
			t.Skip("skipping expensive test")
		}
		patchBody := []byte(`{"value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}`)
		err = rest.Patch(types.MergePatchType).AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "test").
			Body(patchBody).Do(context.TODO()).Error()
		if !apierrors.IsBadRequest(err) {
			t.Errorf("expected success or bad request err, got %v", err)
		}
	})
	t.Run("ApplyPatchType nested patch under limit", func(t *testing.T) {
		if testing.Short() {
			t.Skip("skipping expensive test")
		}
		patchBody := []byte(`{"value":` + strings.Repeat("[", 3*1024*1024/2-100) + strings.Repeat("]", 3*1024*1024/2-100) + `}`)
		err = rest.Patch(types.ApplyPatchType).Param("fieldManager", "test").AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural, "test").
			Body(patchBody).Do(context.TODO()).Error()
		if !apierrors.IsBadRequest(err) {
			t.Errorf("expected bad request err, got %#v", err)
		}
	})
}
