/*
 Copyright 2021. The KubeVela 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 envbinding

import (
	"testing"

	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
	"github.com/oam-dev/kubevela/pkg/oam/util"
)

func Test_EnvBindApp_GenerateConfiguredApplication(t *testing.T) {
	testCases := map[string]struct {
		baseApp     *v1beta1.Application
		envName     string
		envPatch    v1alpha1.EnvPatch
		expectedApp *v1beta1.Application
		selector    *v1alpha1.EnvSelector
	}{
		"normal-test": {
			baseApp: baseApp,
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name: "express-server",
					Type: "webservice",
					Properties: util.Object2RawExtension(map[string]interface{}{
						"image": "busybox",
					}),
					Traits: []v1alpha1.EnvTraitPatch{{
						Type: "ingress-1-20",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"domain": "newTestsvc.example.com",
						}),
					}},
				}},
			},
			expectedApp: &v1beta1.Application{
				TypeMeta: metav1.TypeMeta{
					APIVersion: "v1beta1",
					Kind:       "Application",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "busybox",
							"port":  8000,
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "newTestsvc.example.com",
								"http": map[string]interface{}{
									"/": 8000,
								},
							}),
						}},
					}},
				},
			},
		},
		"add-component": {
			baseApp: baseApp,
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name: "express-server",
					Type: "webservice",
					Traits: []v1alpha1.EnvTraitPatch{{
						Type: "labels",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"test": "label",
						}),
					}},
				}, {
					Name: "new-server",
					Type: "worker",
					Properties: util.Object2RawExtension(map[string]interface{}{
						"image": "busybox",
						"cmd":   []string{"sleep", "1000"},
					}),
					Traits: []v1alpha1.EnvTraitPatch{{
						Type: "labels",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"test": "label",
						}),
					}},
				}},
			},
			expectedApp: &v1beta1.Application{
				TypeMeta: metav1.TypeMeta{
					APIVersion: "v1beta1",
					Kind:       "Application",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
							"port":  8000,
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "testsvc.example.com",
								"http": map[string]interface{}{
									"/": 8000,
								},
							}),
						}, {
							Type: "labels",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"test": "label",
							}),
						}},
					}, {
						Name: "new-server",
						Type: "worker",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "busybox",
							"cmd":   []string{"sleep", "1000"},
						}),
						Traits: []common.ApplicationTrait{{
							Type: "labels",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"test": "label",
							}),
						}},
					}},
				},
			},
		},
		"disable-trait": {
			baseApp: baseApp,
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name: "express-server",
					Type: "webservice",
					Traits: []v1alpha1.EnvTraitPatch{{
						Type:    "ingress-1-20",
						Disable: true,
					}},
				}},
			},
			expectedApp: &v1beta1.Application{
				TypeMeta: metav1.TypeMeta{
					APIVersion: "v1beta1",
					Kind:       "Application",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
							"port":  8000,
						}),
						Traits: []common.ApplicationTrait{},
					}},
				},
			},
		},
		"component-selector": {
			baseApp: baseApp,
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name: "new-server",
					Type: "worker",
					Properties: util.Object2RawExtension(map[string]interface{}{
						"image": "busybox",
					}),
				}},
			},
			selector: &v1alpha1.EnvSelector{
				Components: []string{"new-server"},
			},
			expectedApp: &v1beta1.Application{
				TypeMeta: metav1.TypeMeta{
					APIVersion: "v1beta1",
					Kind:       "Application",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "new-server",
						Type: "worker",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "busybox",
						}),
					}},
				},
			},
		},
		"empty-component-selector": {
			baseApp: baseApp,
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name: "new-server",
					Type: "worker",
					Properties: util.Object2RawExtension(map[string]interface{}{
						"image": "busybox",
					}),
				}},
			},
			selector: &v1alpha1.EnvSelector{
				Components: []string{},
			},
			expectedApp: &v1beta1.Application{
				TypeMeta: metav1.TypeMeta{
					APIVersion: "v1beta1",
					Kind:       "Application",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{},
				},
			},
		},
		"patch-external-revision": {
			baseApp: baseApp,
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name:             "express-server",
					Type:             "webservice",
					ExternalRevision: "external-rev",
				}},
			},
			expectedApp: &v1beta1.Application{
				TypeMeta: metav1.TypeMeta{
					APIVersion: "v1beta1",
					Kind:       "Application",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
							"port":  8000,
						}),
						ExternalRevision: "external-rev",
						Traits: []common.ApplicationTrait{{
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "testsvc.example.com",
								"http": map[string]interface{}{
									"/": 8000,
								},
							}),
						}},
					}},
				},
			},
		},
		"patch-all-comp": {
			baseApp: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a.example.com",
							}),
						}, {
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a-1-20.example.com",
							}),
						}},
					}, {
						Name: "express-worker",
						Type: "worker",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
						}),
					}},
				},
			},
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name: "",
					Type: "",
					Properties: util.Object2RawExtension(map[string]interface{}{
						"image": "busybox",
					}),
					Traits: []v1alpha1.EnvTraitPatch{{
						Type: "ingress",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"domain": "b.example.com",
						}),
					}},
				}},
			},
			expectedApp: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "busybox",
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "b.example.com",
							}),
						}, {
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a-1-20.example.com",
							}),
						}},
					}, {
						Name: "express-worker",
						Type: "worker",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "busybox",
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "b.example.com",
							}),
						}},
					}},
				},
			},
		},
		"patch-type-comp": {
			baseApp: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a.example.com",
							}),
						}, {
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a-1-20.example.com",
							}),
						}},
					}, {
						Name: "express-worker",
						Type: "worker",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
						}),
					}},
				},
			},
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name: "",
					Type: "webservice",
					Properties: util.Object2RawExtension(map[string]interface{}{
						"image": "busybox",
					}),
					Traits: []v1alpha1.EnvTraitPatch{{
						Type: "ingress",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"domain": "b.example.com",
						}),
					}},
				}},
			},
			expectedApp: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "busybox",
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "b.example.com",
							}),
						}, {
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a-1-20.example.com",
							}),
						}},
					}, {
						Name: "express-worker",
						Type: "worker",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
						}),
					}},
				},
			},
		},
		"patch-without-type-specified": {
			baseApp: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a.example.com",
							}),
						}, {
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a-1-20.example.com",
							}),
						}},
					}, {
						Name: "express-worker",
						Type: "worker",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
						}),
					}},
				},
			},
			envName: "prod",
			envPatch: v1alpha1.EnvPatch{
				Components: []v1alpha1.EnvComponentPatch{{
					Name: "express-worker",
					Type: "",
					Properties: util.Object2RawExtension(map[string]interface{}{
						"image": "busybox",
					}),
					Traits: []v1alpha1.EnvTraitPatch{{
						Type: "ingress",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"domain": "b.example.com",
						}),
					}},
				}},
			},
			expectedApp: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "express-server",
						Type: "webservice",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "crccheck/hello-world",
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a.example.com",
							}),
						}, {
							Type: "ingress-1-20",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "a-1-20.example.com",
							}),
						}},
					}, {
						Name: "express-worker",
						Type: "worker",
						Properties: util.Object2RawExtension(map[string]interface{}{
							"image": "busybox",
						}),
						Traits: []common.ApplicationTrait{{
							Type: "ingress",
							Properties: util.Object2RawExtension(map[string]interface{}{
								"domain": "b.example.com",
							}),
						}},
					}},
				},
			},
		},
	}

	for name, tc := range testCases {
		t.Run(name, func(t *testing.T) {
			app, err := PatchApplication(tc.baseApp, &tc.envPatch, tc.selector)
			r := require.New(t)
			r.NoError(err)
			r.Equal(tc.expectedApp, app)
		})
	}
}

var baseApp = &v1beta1.Application{
	TypeMeta: metav1.TypeMeta{
		APIVersion: "v1beta1",
		Kind:       "Application",
	},
	ObjectMeta: metav1.ObjectMeta{
		Name: "test",
	},
	Spec: v1beta1.ApplicationSpec{
		Components: []common.ApplicationComponent{{
			Name: "express-server",
			Type: "webservice",
			Properties: util.Object2RawExtension(map[string]interface{}{
				"image": "crccheck/hello-world",
				"port":  8000,
			}),
			Traits: []common.ApplicationTrait{{
				Type: "ingress-1-20",
				Properties: util.Object2RawExtension(map[string]interface{}{
					"domain": "testsvc.example.com",
					"http": map[string]interface{}{
						"/": 8000,
					},
				}),
			}},
		}},
	},
}
