package operate

import (
	"testing"

	"github.com/google/go-cmp/cmp"
	v1 "github.com/tidbcloud/infra-component/api/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

func TestRenderComponent(t *testing.T) {
	type opt struct {
		Name    string
		Payload string
	}

	testCases := []struct {
		name        string
		component   v1.Component
		options     interface{}
		wantError   bool
		wantObjects []runtime.Object
	}{
		{
			name: "no objects",
			component: v1.Component{
				Spec: v1.ComponentSpec{Objects: []v1.ComponentObject{}},
			},
			wantError:   false,
			wantObjects: nil,
		},
		{
			name: "single object",
			component: v1.Component{
				Spec: v1.ComponentSpec{Objects: []v1.ComponentObject{
					{
						Name:         "secret",
						DeletePolicy: v1.Delete,
						Template: `apiVersion: v1
kind: Secret
metadata:
  name: {{ .Name }}
data:
  payload: {{ .Payload }}`,
					},
				}},
			},
			options: opt{
				Name:    "test",
				Payload: "aGVsbG8=",
			},
			wantError: false,
			wantObjects: []runtime.Object{
				&corev1.Secret{
					TypeMeta:   metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
					ObjectMeta: metav1.ObjectMeta{Name: "test"},
					Data: map[string][]byte{
						"payload": []byte("hello"),
					},
				},
			},
		},
		{
			name: "multiple objects",
			component: v1.Component{
				Spec: v1.ComponentSpec{Objects: []v1.ComponentObject{
					{
						Name:         "secret",
						DeletePolicy: v1.Delete,
						Template: `apiVersion: v1
kind: Secret
metadata:
  name: {{ .Name }}
data:
  payload: {{ .Payload }}`,
					},
					{
						Name:         "secret",
						DeletePolicy: v1.Delete,
						Template: `apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ .Name }}`,
					},
				}},
			},
			options: opt{
				Name:    "test",
				Payload: "aGVsbG8=",
			},
			wantError: false,
			wantObjects: []runtime.Object{
				&corev1.Secret{
					TypeMeta:   metav1.TypeMeta{Kind: "Secret", APIVersion: "v1"},
					ObjectMeta: metav1.ObjectMeta{Name: "test"},
					Data: map[string][]byte{
						"payload": []byte("hello"),
					},
				},
				&corev1.ServiceAccount{
					TypeMeta:   metav1.TypeMeta{Kind: "ServiceAccount", APIVersion: "v1"},
					ObjectMeta: metav1.ObjectMeta{Name: "test"},
				},
			},
		},
		{
			name: "invalid template",
			component: v1.Component{
				Spec: v1.ComponentSpec{Objects: []v1.ComponentObject{
					{
						Name:         "secret",
						DeletePolicy: v1.Delete,
						Template: `apiVersion: v1
kind: Secret
metadata:
  name: {{ !Name }}
data:
  payload: {{ .Payload }}`,
					},
				}},
			},
			options: opt{
				Name:    "test",
				Payload: "aGVsbG8=",
			},
			wantError: true,
		},
		{
			name: "missing field in the option",
			component: v1.Component{
				Spec: v1.ComponentSpec{Objects: []v1.ComponentObject{
					{
						Name:         "secret",
						DeletePolicy: v1.Delete,
						Template: `apiVersion: v1
kind: Secret
metadata:
  name: {{ .Name }}
data:
  payload: {{ .Payload }}
  data: {{ .Data }}`,
					},
				}},
			},
			options: opt{
				Name:    "test",
				Payload: "aGVsbG8=",
			},
			wantError: true,
		},
		{
			name: "unknown kind",
			component: v1.Component{
				Spec: v1.ComponentSpec{Objects: []v1.ComponentObject{
					{
						Name:         "unknown",
						DeletePolicy: v1.Delete,
						Template: `apiVersion: v1
kind: Unknown
metadata:
  name: {{ .Name }}`,
					},
				}},
			},
			options: opt{
				Name: "test",
			},
			wantError: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			objects, err := RenderComponent(tc.component, tc.options)
			gotError := err != nil
			if gotError != tc.wantError {
				t.Fatalf("RenderComponent got error = %t, want error = %t", gotError, tc.wantError)
			}
			if diff := cmp.Diff(objects, tc.wantObjects); diff != "" {
				t.Fatalf("RenderComponent diff = %s", diff)
			}
		})
	}
}
