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

package kio_test

import (
	"bytes"
	"testing"

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

func TestPrinter_Write_Package_Structure(t *testing.T) {
	in := `kind: Deployment
metadata:
  labels:
    app: nginx3
  name: foo
  namespace: default
  annotations:
    app: nginx3
    config.kubernetes.io/path: foo-package/3/f3.yaml
spec:
  replicas: 1
---
kind: Deployment
metadata:
  labels:
    app: nginx2
  name: foo
  namespace: default
  annotations:
    app: nginx2
    config.kubernetes.io/path: foo-package/f1.yaml
spec:
  replicas: 1
---
kind: Deployment
metadata:
  labels:
    app: nginx
  annotations:
    app: nginx
    config.kubernetes.io/path: bar-package/f2.yaml
  name: bar
spec:
  replicas: 3
---
kind: Service
metadata:
  name: foo
  namespace: default
  annotations:
    app: nginx
    config.kubernetes.io/path: foo-package/f1.yaml
spec:
  selector:
    app: nginx
`
	out := &bytes.Buffer{}
	err := Pipeline{
		Inputs:  []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}},
		Outputs: []Writer{TreeWriter{Writer: out, Structure: TreeStructurePackage}},
	}.Execute()
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	if !assert.Equal(t, `
├── bar-package
│   └── [f2.yaml]  Deployment bar
└── foo-package
    ├── [f1.yaml]  Deployment default/foo
    ├── [f1.yaml]  Service default/foo
    └── 3
        └── [f3.yaml]  Deployment default/foo
`, out.String()) {
		t.FailNow()
	}
}

func TestPrinter_Write_Package_Structure_base(t *testing.T) {
	in := `kind: Deployment
metadata:
  labels:
    app: nginx3
  name: foo
  namespace: default
  annotations:
    app: nginx3
    config.kubernetes.io/path: f3.yaml
spec:
  replicas: 1
---
kind: Deployment
metadata:
  labels:
    app: nginx2
  name: foo
  namespace: default
  annotations:
    app: nginx2
    config.kubernetes.io/path: foo-package/f1.yaml
spec:
  replicas: 1
---
kind: Deployment
metadata:
  labels:
    app: nginx
  annotations:
    app: nginx
    config.kubernetes.io/package: 
    config.kubernetes.io/path: bar-package/f2.yaml
  name: bar
spec:
  replicas: 3
---
kind: Service
metadata:
  name: foo
  namespace: default
  annotations:
    app: nginx
    config.kubernetes.io/package: .
    config.kubernetes.io/path: f1.yaml
spec:
  selector:
    app: nginx
`
	out := &bytes.Buffer{}
	err := Pipeline{
		Inputs:  []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}},
		Outputs: []Writer{TreeWriter{Writer: out, Structure: TreeStructurePackage}},
	}.Execute()
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	if !assert.Equal(t, `
├── [f1.yaml]  Service default/foo
├── [f3.yaml]  Deployment default/foo
├── bar-package
│   └── [f2.yaml]  Deployment bar
└── foo-package
    └── [f1.yaml]  Deployment default/foo
`, out.String()) {
		t.FailNow()
	}
}

func TestPrinter_Write_Package_Structure_sort(t *testing.T) {
	in := `apiVersion: extensions/v1
kind: Deployment
metadata:
  labels:
    app: nginx2
  name: foo3
  namespace: default
  annotations:
    app: nginx2
    config.kubernetes.io/path: f1.yaml
spec:
  replicas: 1
---
apiVersion: extensions/v1
kind: Deployment
metadata:
  labels:
    app: nginx2
  name: foo3
  namespace: default
  annotations:
    app: nginx2
    config.kubernetes.io/path: f1.yaml
spec:
  replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx2
  name: foo3
  namespace: default
  annotations:
    app: nginx2
    config.kubernetes.io/path: f1.yaml
spec:
  replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx2
  name: foo2
  namespace: default2
  annotations:
    app: nginx2
    config.kubernetes.io/path: f1.yaml
spec:
  replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx3
  name: foo
  namespace: default
  annotations:
    app: nginx3
    config.kubernetes.io/path: f1.yaml
spec:
  replicas: 1
---
kind: Deployment
metadata:
  labels:
    app: nginx
  annotations:
    app: nginx
    config.kubernetes.io/path: bar-package/f2.yaml
  name: bar
spec:
  replicas: 3
---
kind: Service
metadata:
  name: foo
  namespace: default
  annotations:
    app: nginx
    config.kubernetes.io/path: f1.yaml
spec:
  selector:
    app: nginx
`
	out := &bytes.Buffer{}
	err := Pipeline{
		Inputs:  []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}},
		Outputs: []Writer{TreeWriter{Writer: out, Structure: TreeStructurePackage}},
	}.Execute()
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	if !assert.Equal(t, `
├── [f1.yaml]  Deployment default/foo
├── [f1.yaml]  Service default/foo
├── [f1.yaml]  Deployment default/foo3
├── [f1.yaml]  Deployment default/foo3
├── [f1.yaml]  Deployment default/foo3
├── [f1.yaml]  Deployment default2/foo2
└── bar-package
    └── [f2.yaml]  Deployment bar
`, out.String()) {
		t.FailNow()
	}
}

func TestPrinter_metaError(t *testing.T) {
	out := &bytes.Buffer{}
	err := TreeWriter{Writer: out}.Write([]*yaml.RNode{{}})
	if !assert.NoError(t, err) {
		t.FailNow()
	}
	if !assert.Equal(t, `
`, out.String()) {
		t.FailNow()
	}
}

func TestPrinter_Write_Graph_Structure(t *testing.T) {
	in := `
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-0
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: apps/v1
    kind: StatefulSet
    name: cockroachdb
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-1
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: apps/v1
    kind: StatefulSet
    name: cockroachdb
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-2
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: apps/v1
    kind: StatefulSet
    name: cockroachdb
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.0
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cockroachdb
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: app.k8s.io/v1beta1
    kind: Application
    name: myapp
spec:
  replicas: 3
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Service
metadata:
  name: cockroachdb
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: app.k8s.io/v1beta1
    kind: Application
    name: myapp
---
apiVersion: app.k8s.io/v1beta1
kind: Application
metadata:
  labels:
    app.kubernetes.io/name: myapp
  name: myapp
  namespace: myapp-staging
`
	out := &bytes.Buffer{}
	err := Pipeline{
		Inputs:  []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}},
		Outputs: []Writer{TreeWriter{Writer: out, Structure: TreeStructureGraph}},
	}.Execute()
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	if !assert.Equal(t, `.
└── [Resource]  Application myapp-staging/myapp
    ├── [Resource]  Service myapp-staging/cockroachdb
    └── [Resource]  StatefulSet myapp-staging/cockroachdb
        ├── [Resource]  Pod myapp-staging/cockroachdb-0
        ├── [Resource]  Pod myapp-staging/cockroachdb-1
        └── [Resource]  Pod myapp-staging/cockroachdb-2
`, out.String()) {
		t.FailNow()
	}
}

func TestPrinter_Write_Structure_Defaulting_when_ownerRefs_present(t *testing.T) {
	in := `
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-0
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: apps/v1
    kind: StatefulSet
    name: cockroachdb
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-1
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: apps/v1
    kind: StatefulSet
    name: cockroachdb
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-2
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: apps/v1
    kind: StatefulSet
    name: cockroachdb
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.0
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cockroachdb
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: app.k8s.io/v1beta1
    kind: Application
    name: myapp
spec:
  replicas: 3
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Service
metadata:
  name: cockroachdb
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: app.k8s.io/v1beta1
    kind: Application
    name: myapp
---
apiVersion: app.k8s.io/v1beta1
kind: Application
metadata:
  labels:
    app.kubernetes.io/name: myapp
  name: myapp
  namespace: myapp-staging
`
	out := &bytes.Buffer{}
	err := Pipeline{
		Inputs:  []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}},
		Outputs: []Writer{TreeWriter{Writer: out}}, // Structure unspecified
	}.Execute()
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	if !assert.Equal(t, `.
└── [Resource]  Application myapp-staging/myapp
    ├── [Resource]  Service myapp-staging/cockroachdb
    └── [Resource]  StatefulSet myapp-staging/cockroachdb
        ├── [Resource]  Pod myapp-staging/cockroachdb-0
        ├── [Resource]  Pod myapp-staging/cockroachdb-1
        └── [Resource]  Pod myapp-staging/cockroachdb-2
`, out.String()) {
		t.FailNow()
	}
}

func TestPrinter_Write_Structure_Defaulting_when_ownerRefs_absent(t *testing.T) {
	in := `
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-0
  namespace: myapp-staging
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-1
  namespace: myapp-staging
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Pod
metadata:
  name: cockroachdb-2
  namespace: myapp-staging
spec:
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.0
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cockroachdb
  namespace: myapp-staging
spec:
  replicas: 3
  containers:
  - name: cockroachdb
    image: cockraochdb:1.1.1
---
apiVersion: v1
kind: Service
metadata:
  name: cockroachdb
  namespace: myapp-staging
---
apiVersion: app.k8s.io/v1beta1
kind: Application
metadata:
  labels:
    app.kubernetes.io/name: myapp
  name: myapp
  namespace: myapp-staging
`
	out := &bytes.Buffer{}
	err := Pipeline{
		Inputs:  []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}},
		Outputs: []Writer{TreeWriter{Writer: out}}, // Structure unspecified
	}.Execute()
	if !assert.NoError(t, err) {
		t.FailNow()
	}

	if !assert.Equal(t, `
├── [.]  Service myapp-staging/cockroachdb
├── [.]  StatefulSet myapp-staging/cockroachdb
├── [.]  Pod myapp-staging/cockroachdb-0
├── [.]  Pod myapp-staging/cockroachdb-1
├── [.]  Pod myapp-staging/cockroachdb-2
└── [.]  Application myapp-staging/myapp
`, out.String()) {
		t.FailNow()
	}
}

func TestPrinter_Write_error_when_owner_missing(t *testing.T) {
	in := `
---
apiVersion: v1
kind: Service
metadata:
  name: cockroachdb
  namespace: myapp-staging
  ownerReferences:
  - apiVersion: app.k8s.io/v1beta1
    kind: Application
    name: nginx
---
apiVersion: app.k8s.io/v1beta1
kind: Application
metadata:
  labels:
    app.kubernetes.io/name: myapp
  name: myapp
  namespace: myapp-staging
`
	out := &bytes.Buffer{}
	err := Pipeline{
		Inputs:  []Reader{&ByteReader{Reader: bytes.NewBufferString(in)}},
		Outputs: []Writer{TreeWriter{Writer: out}},
	}.Execute()
	require.Error(t, err)
	assert.Equal(t, "owner 'Application myapp-staging/nginx' not found in input, but found as an owner of input objects", err.Error())
}
