// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package openapi

import (
	"fmt"
	"io/ioutil"
	"testing"

	"github.com/stretchr/testify/assert"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

func TestAddSchema(t *testing.T) {
	// reset package vars
	globalSchema = openapiData{}

	err := AddSchema(additionalSchema)
	if !assert.NoError(t, err) {
		t.FailNow()
	}
	s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.config.setters.replicas"}`, Schema())
	if !assert.Greater(t, len(globalSchema.schema.Definitions), 200) {
		t.FailNow()
	}
	if !assert.NoError(t, err) {
		t.FailNow()
	}
	assert.Equal(t, `map[x-kustomize:map[setBy:Jane setter:map[name:replicas value:5]]]`,
		fmt.Sprintf("%v", s.Schema.Extensions))
}

var additionalSchema = []byte(`
{
  "definitions": {
    "io.k8s.config.setters.replicas": {
      "description": "replicas description.",
      "type": "integer",
      "x-kustomize": {"setBy":"Jane","setter": {"name":"replicas","value":"5"}}
    }
  },
  "invalid": "field"
}
`)

func TestSchemaForResourceType(t *testing.T) {
	// reset package vars
	globalSchema = openapiData{}

	s := SchemaForResourceType(
		yaml.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"})
	if !assert.NotNil(t, s) {
		t.FailNow()
	}

	f := s.Field("spec")
	if !assert.NotNil(t, f) {
		t.FailNow()
	}
	if !assert.Equal(t, "DeploymentSpec is the specification of the desired behavior of the Deployment.",
		f.Schema.Description) {
		t.FailNow()
	}

	replicas := f.Field("replicas")
	if !assert.NotNil(t, replicas) {
		t.FailNow()
	}
	if !assert.Equal(t, "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.",
		replicas.Schema.Description) {
		t.FailNow()
	}

	temp := f.Field("template")
	if !assert.NotNil(t, temp) {
		t.FailNow()
	}
	if !assert.Equal(t, "PodTemplateSpec describes the data a pod should have when created from a template",
		temp.Schema.Description) {
		t.FailNow()
	}

	containers := temp.Field("spec").Field("containers").Elements()
	if !assert.NotNil(t, containers) {
		t.FailNow()
	}

	targetPort := containers.Field("ports").Elements().Field("containerPort")
	if !assert.NotNil(t, targetPort) {
		t.FailNow()
	}
	if !assert.Equal(t, "Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536.",
		targetPort.Schema.Description) {
		t.FailNow()
	}

	arg := containers.Field("args").Elements()
	if !assert.NotNil(t, arg) {
		t.FailNow()
	}
	if !assert.Equal(t, "string", arg.Schema.Type[0]) {
		t.FailNow()
	}
}

func TestSchemaFromFile(t *testing.T) {
	ResetOpenAPI()
	inputyaml := `
openAPI:
  definitions:
    io.k8s.cli.setters.image-name:
      x-k8s-cli:
        setter:
          name: image-name
          value: "nginx"
 `
	f, err := ioutil.TempFile("", "openapi-")
	if !assert.NoError(t, err) {
		t.FailNow()
	}
	if !assert.NoError(t, ioutil.WriteFile(f.Name(), []byte(inputyaml), 0600)) {
		t.FailNow()
	}

	sc, err := SchemaFromFile(f.Name())
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.cli.setters.image-name"}`, sc)

	if !assert.NoError(t, err) {
		t.FailNow()
	}
	if !assert.Greater(t, len(sc.Definitions), 0) {
		t.FailNow()
	}
	assert.Equal(t, `map[x-k8s-cli:map[setter:map[name:image-name value:nginx]]]`,
		fmt.Sprintf("%v", s.Schema.Extensions))
}

func TestPopulateDefsInOpenAPI_Substitution(t *testing.T) {
	ResetOpenAPI()
	inputyaml := `
openAPI:
  definitions:
    io.k8s.cli.setters.image-name:
      x-k8s-cli:
        setter:
          name: image-name
          value: "nginx"
    io.k8s.cli.setters.image-tag:
      x-k8s-cli:
        setter:
          name: image-tag
          value: "1.8.1"
    io.k8s.cli.substitutions.image:
      x-k8s-cli:
        substitution:
          name: image
          pattern: IMAGE_NAME:IMAGE_TAG
          values:
          - marker: "IMAGE_NAME"
            ref: "#/definitions/io.k8s.cli.setters.image-name"
          - marker: "IMAGE_TAG"
            ref: "#/definitions/io.k8s.cli.setters.image-tag"
 `

	f, err := ioutil.TempFile("", "openapi-")
	if !assert.NoError(t, err) {
		t.FailNow()
	}
	if !assert.NoError(t, ioutil.WriteFile(f.Name(), []byte(inputyaml), 0600)) {
		t.FailNow()
	}

	sc, err := SchemaFromFile(f.Name())
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	s, err := GetSchema(`{"$ref": "#/definitions/io.k8s.cli.substitutions.image"}`, sc)

	if !assert.NoError(t, err) {
		t.FailNow()
	}
	if !assert.Equal(t, len(sc.Definitions), 3) {
		t.FailNow()
	}

	assert.Equal(t,
		`map[x-k8s-cli:map[substitution:map[name:image pattern:IMAGE_NAME:IMAGE_TAG`+
			` values:[map[marker:IMAGE_NAME ref:#/definitions/io.k8s.cli.setters.image-name]`+
			` map[marker:IMAGE_TAG ref:#/definitions/io.k8s.cli.setters.image-tag]]]]]`,
		fmt.Sprintf("%v", s.Schema.Extensions))
}

func TestAddSchemaFromFile_empty(t *testing.T) {
	ResetOpenAPI()
	inputyaml := `
kind: Example
 `

	f, err := ioutil.TempFile("", "openapi-")
	if !assert.NoError(t, err) {
		t.FailNow()
	}
	if !assert.NoError(t, ioutil.WriteFile(f.Name(), []byte(inputyaml), 0600)) {
		t.FailNow()
	}

	sc, err := SchemaFromFile(f.Name())
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	if !assert.Nil(t, sc) {
		t.FailNow()
	}
}

func TestIsNamespaceScoped_builtin(t *testing.T) {
	testCases := []struct {
		name               string
		typeMeta           yaml.TypeMeta
		expectIsFound      bool
		expectIsNamespaced bool
	}{
		{
			name: "namespacescoped resource",
			typeMeta: yaml.TypeMeta{
				APIVersion: "apps/v1",
				Kind:       "Deployment",
			},
			expectIsFound:      true,
			expectIsNamespaced: true,
		},
		{
			name: "clusterscoped resource",
			typeMeta: yaml.TypeMeta{
				APIVersion: "v1",
				Kind:       "Namespace",
			},
			expectIsFound:      true,
			expectIsNamespaced: false,
		},
		{
			name: "unknown resource",
			typeMeta: yaml.TypeMeta{
				APIVersion: "custom.io/v1",
				Kind:       "Custom",
			},
			expectIsFound: false,
		},
	}

	for i := range testCases {
		test := testCases[i]
		t.Run(test.name, func(t *testing.T) {
			ResetOpenAPI()
			isNamespaceable, isFound := IsNamespaceScoped(test.typeMeta)

			if !test.expectIsFound {
				assert.False(t, isFound)
				return
			}
			assert.True(t, isFound)
			assert.Equal(t, test.expectIsNamespaced, isNamespaceable)
		})
	}
}

func TestIsNamespaceScoped_custom(t *testing.T) {
	SuppressBuiltInSchemaUse()
	err := AddSchema([]byte(`
{
  "definitions": {},
  "paths": {
    "/apis/custom.io/v1/namespaces/{namespace}/customs/{name}": {
      "get": {
        "x-kubernetes-action": "get",
        "x-kubernetes-group-version-kind": {
          "group": "custom.io",
          "kind": "Custom",
          "version": "v1"
        }
      }
    },
    "/apis/custom.io/v1/clustercustoms": {
      "get": {
        "x-kubernetes-action": "get",
        "x-kubernetes-group-version-kind": {
          "group": "custom.io",
          "kind": "ClusterCustom",
          "version": "v1"
        }
      }
    }
  }
}
`))
	assert.NoError(t, err)

	isNamespaceable, isFound := IsNamespaceScoped(yaml.TypeMeta{
		APIVersion: "custom.io/v1",
		Kind:       "ClusterCustom",
	})
	assert.True(t, isFound)
	assert.False(t, isNamespaceable)

	isNamespaceable, isFound = IsNamespaceScoped(yaml.TypeMeta{
		APIVersion: "custom.io/v1",
		Kind:       "Custom",
	})
	assert.True(t, isFound)
	assert.True(t, isNamespaceable)
}
