/*
 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

     https://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 extlib

import (
	"testing"

	"bytes"
	"strings"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"sigs.k8s.io/kustomize/kyaml/kio"
	"sigs.k8s.io/kustomize/kyaml/yaml"

	kfilters "sigs.k8s.io/kustomize/kyaml/kio/filters"
)

func TestKFilter(t *testing.T) {
	testCases := []struct {
		in          string
		expectedOut kio.Filter
	}{
		{
			in: `
kind: GrepFilter
path:
- metadata
- annotations
- test-annotation
value: ^x$
invertMatch: true
`,
			expectedOut: kfilters.GrepFilter{
				Path: []string{
					"metadata",
					"annotations",
					"test-annotation",
				},
				Value:       "^x$",
				InvertMatch: true,
			},
		},
		{
			in: `
kind: NonExistentFilter
path:
- metadata
`,
			expectedOut: nil,
		},
		{
			in: `
kind: GrepFilter
path: "incorrectdata"
`,
			expectedOut: nil,
		},
		{
			in: `
kind: Modifier
pipeline: "incorrectdata"
`,
			expectedOut: nil,
		},
	}

	for _, tc := range testCases {
		r := kFilter(tc.in)

		// GrepFilter is a special case
		grepFilter, ok := r.(kfilters.GrepFilter)
		if ok {
			require.NotNil(t, grepFilter.Compare)
			grepFilter.Compare = nil
			r = grepFilter
		}

		assert.Equal(t, tc.expectedOut, r)
	}
}

func TestKPipe(t *testing.T) {
	testCases := []struct {
		in          string
		filters     string
		expectedOut string
	}{
		{
			in: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf2
---
apiVersion: v1
kind: Deployment
metadata:
  name: cf1
`,
			filters: `
kind: GrepFilter
path:
- metadata
- name
value: cf1
---
kind: GrepFilter
path:
- kind
value: ConfigMap
`,
			expectedOut: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
`,
		},
		{
			in: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
`,
			filters: `
kind: InvalidFilter
`,
			expectedOut: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
`,
		},
	}

	for _, tc := range testCases {
		// convert in to []*yaml.RNode
		b := kio.PackageBuffer{}
		p := kio.Pipeline{
			Inputs:  []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(tc.in)}},
			Outputs: []kio.Writer{&b},
		}
		err := p.Execute()
		require.NoError(t, err)

		// get list of filters
		kfilters := []interface{}{}
		for _, flt := range strings.Split(tc.filters, "\n---\n") {
			kfilters = append(kfilters, kFilter(flt))
		}

		nodes := kPipe(b.Nodes, kfilters)

		// convert to string and compare with expected
		out := &bytes.Buffer{}
		err = kio.ByteWriter{Writer: out}.Write(nodes)
		require.NoError(t, err)
		assert.Equal(t, tc.expectedOut[1:], out.String())
	}
}

func TestYFilter(t *testing.T) {
	testCases := []struct {
		in          string
		expectedOut yaml.Filter
	}{
		{
			in: `
kind: PathGetter
path: ["data", "fld1"]
`,
			expectedOut: &yaml.PathGetter{
				Kind: "PathGetter",
				Path: []string{
					"data",
					"fld1",
				},
			},
		},
		{in: `
kind: PathGetter
path: "data"
`,
			expectedOut: nil,
		},
		{in: `
kind: nonExistingFilter
path: "data"
`,
			expectedOut: nil,
		},
	}

	for _, tc := range testCases {
		out := yFilter(tc.in)
		assert.Equal(t, tc.expectedOut, out)
	}
}

func TestYPipe(t *testing.T) {
	testCases := []struct {
		in          string
		filters     string
		expectedIn  string
		expectedOut string
	}{
		{
			in: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
`,
			filters: `
kind: PathGetter
path: ["metadata"]
---
kind: FieldSetter
name: "name"
stringValue: "cf2"
`,
			expectedIn: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf2
`,
			expectedOut: `
cf2
`,
		},
		{
			in: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
`,
			filters: `
kind: InvalidPathGetter
path: ["metadata"]
`,
		},
		{
			in: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
`,
			filters: `
kind: PathGetter
path: ["xmetadata"]
---
kind: FieldSetter
name: "namex"
stringValue: "cf2"
`,
			expectedIn: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
`,
		},
	}
	for _, tc := range testCases {
		inRNode, err := yaml.Parse(tc.in)
		require.NoError(t, err)

		// get list of filters
		yfilters := []interface{}{}
		for _, flt := range strings.Split(tc.filters, "\n---\n") {
			yfilters = append(yfilters, yFilter(flt))
		}

		outRNode := yPipe(inRNode, yfilters)

		if tc.expectedOut != "" {
			require.NotNil(t, outRNode)
			out, err := outRNode.String()
			require.NoError(t, err)
			assert.Equal(t, tc.expectedOut[1:], out)
		}

		if tc.expectedIn != "" {
			in, err := inRNode.String()
			require.NoError(t, err)
			assert.Equal(t, tc.expectedIn[1:], in)
		}
	}
}

func TestYValue(t *testing.T) {
	testCases := []struct {
		in          string
		expectedOut interface{}
	}{
		{
			in: `
x
`,
			expectedOut: "x",
		},
		{
			in: `
kind: x
value: b
list:
- a
- b
`,
			expectedOut: map[string]interface{}{
				"kind": "x",
				"list": []interface{}{
					"a",
					"b",
				},
				"value": "b",
			},
		},
	}

	for _, tc := range testCases {
		inRNode, err := yaml.Parse(tc.in)
		require.NoError(t, err)

		out := yValue(inRNode)
		assert.Equal(t, tc.expectedOut, out)
	}
}

func TestKYFilter(t *testing.T) {
	testCases := []struct {
		in          string
		filters     string
		expectedOut string
	}{
		{
			in: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
  labels: {}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf2
  labels: {}
---
apiVersion: v1
kind: Deployment
metadata:
  name: cf1
  labels: {}
`,
			filters: `
kind: PathGetter
path: ["metadata", "labels"]
---
kind: FieldSetter
name: "newlabel"
stringValue: "newvalue"
`,
			expectedOut: `
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf1
  labels: {newlabel: newvalue}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cf2
  labels: {newlabel: newvalue}
---
apiVersion: v1
kind: Deployment
metadata:
  name: cf1
  labels: {newlabel: newvalue}
`,
		},
	}
	for _, tc := range testCases {
		// convert in to []*yaml.RNode
		b := kio.PackageBuffer{}
		p := kio.Pipeline{
			Inputs:  []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(tc.in)}},
			Outputs: []kio.Writer{&b},
		}
		err := p.Execute()
		require.NoError(t, err)

		// get list of filters
		yfilters := []interface{}{}
		for _, flt := range strings.Split(tc.filters, "\n---\n") {
			yfilters = append(yfilters, yFilter(flt))
		}

		kfilters := []interface{}{newKYFilter(yfilters)}
		nodes := kPipe(b.Nodes, kfilters)

		// convert to string and compare with expected
		out := &bytes.Buffer{}
		err = kio.ByteWriter{Writer: out}.Write(nodes)
		require.NoError(t, err)
		assert.Equal(t, tc.expectedOut[1:], out.String())
	}
}