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

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"

	"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"
	common2 "github.com/oam-dev/kubevela/pkg/utils/common"
)

func TestWorkflowStepGenerator(t *testing.T) {
	r := require.New(t)
	cli := fake.NewClientBuilder().WithScheme(common2.Scheme).WithObjects(&workflowv1alpha1.Workflow{
		ObjectMeta: v1.ObjectMeta{
			Name:      "ref-wf",
			Namespace: "test",
		},
		WorkflowSpec: workflowv1alpha1.WorkflowSpec{
			Steps: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name: "manual-approve",
					Type: "suspend",
				},
			}, {
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name: "deploy",
					Type: "deploy",
				},
			}},
		},
	}).Build()
	testCases := map[string]struct {
		input    []workflowv1alpha1.WorkflowStep
		app      *v1beta1.Application
		output   []workflowv1alpha1.WorkflowStep
		hasError bool
	}{
		"apply-component-with-existing-steps": {
			input: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "example-comp-1",
					Type:       "apply-component",
					Properties: &runtime.RawExtension{Raw: []byte(`{"component":"example-comp-1"}`)},
				},
			}},
			app: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "example-comp-1",
					}, {
						Name: "example-comp-2",
					}},
				},
			},
			output: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "example-comp-1",
					Type:       "apply-component",
					Properties: &runtime.RawExtension{Raw: []byte(`{"component":"example-comp-1"}`)},
				},
			}},
		},
		"apply-component-with-no-steps": {
			input: []workflowv1alpha1.WorkflowStep{},
			app: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "example-comp-1",
					}, {
						Name: "example-comp-2",
					}},
				},
			},
			output: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "example-comp-1",
					Type:       "apply-component",
					Properties: &runtime.RawExtension{Raw: []byte(`{"component":"example-comp-1"}`)},
				},
			}, {
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "example-comp-2",
					Type:       "apply-component",
					Properties: &runtime.RawExtension{Raw: []byte(`{"component":"example-comp-2"}`)},
				},
			}},
		},
		"env-binding-bad": {
			input: []workflowv1alpha1.WorkflowStep{},
			app: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "example-comp-1",
					}},
					Policies: []v1beta1.AppPolicy{{
						Name:       "example-policy",
						Type:       v1alpha1.EnvBindingPolicyType,
						Properties: &runtime.RawExtension{Raw: []byte(`bad value`)},
					}},
				},
			},
			hasError: true,
		},
		"env-binding-correct": {
			input: []workflowv1alpha1.WorkflowStep{},
			app: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "example-comp-1",
					}},
					Policies: []v1beta1.AppPolicy{{
						Name:       "example-policy",
						Type:       v1alpha1.EnvBindingPolicyType,
						Properties: &runtime.RawExtension{Raw: []byte(`{"envs":[{"name":"env-1"},{"name":"env-2"}]}`)},
					}},
				},
			},
			output: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy-example-policy-env-1",
					Type:       "deploy2env",
					Properties: &runtime.RawExtension{Raw: []byte(`{"env":"env-1","policy":"example-policy"}`)},
				},
			}, {
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy-example-policy-env-2",
					Type:       "deploy2env",
					Properties: &runtime.RawExtension{Raw: []byte(`{"env":"env-2","policy":"example-policy"}`)},
				},
			}},
		},
		"deploy-workflow": {
			input: []workflowv1alpha1.WorkflowStep{},
			app: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "example-comp-1",
					}},
					Policies: []v1beta1.AppPolicy{{
						Name: "example-topology-policy-1",
						Type: v1alpha1.TopologyPolicyType,
					}, {
						Name: "example-topology-policy-2",
						Type: v1alpha1.TopologyPolicyType,
					}, {
						Name: "example-override-policy-1",
						Type: v1alpha1.OverridePolicyType,
					}, {
						Name: "example-override-policy-2",
						Type: v1alpha1.OverridePolicyType,
					}},
				},
			},
			output: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy-example-topology-policy-1",
					Type:       "deploy",
					Properties: &runtime.RawExtension{Raw: []byte(`{"policies":["example-override-policy-1","example-override-policy-2","example-topology-policy-1"]}`)},
				},
			}, {
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy-example-topology-policy-2",
					Type:       "deploy",
					Properties: &runtime.RawExtension{Raw: []byte(`{"policies":["example-override-policy-1","example-override-policy-2","example-topology-policy-2"]}`)},
				},
			}},
		},
		"deploy-with-ref-without-po-workflow": {
			input: []workflowv1alpha1.WorkflowStep{},
			app: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "example-comp",
						Type: "ref-objects",
					}},
				},
			},
			output: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy",
					Type:       "deploy",
					Properties: &runtime.RawExtension{Raw: []byte(`{"policies":[]}`)},
				},
			}},
		},
		"pre-approve-workflow": {
			input: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy-example-topology-policy-1",
					Type:       "deploy",
					Properties: &runtime.RawExtension{Raw: []byte(`{"policies":["example-topology-policy-1"]}`)},
				},
			}, {
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy-example-topology-policy-2",
					Type:       "deploy",
					Properties: &runtime.RawExtension{Raw: []byte(`{"auto":false,"policies":["example-topology-policy-2"]}`)},
				},
			}},
			app: &v1beta1.Application{
				Spec: v1beta1.ApplicationSpec{
					Components: []common.ApplicationComponent{{
						Name: "example-comp-1",
					}},
				},
			},
			output: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy-example-topology-policy-1",
					Type:       "deploy",
					Properties: &runtime.RawExtension{Raw: []byte(`{"policies":["example-topology-policy-1"]}`)},
				},
			}, {
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name: "manual-approve-deploy-example-topology-policy-2",
					Type: "suspend",
				},
			}, {
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name:       "deploy-example-topology-policy-2",
					Type:       "deploy",
					Properties: &runtime.RawExtension{Raw: []byte(`{"auto":false,"policies":["example-topology-policy-2"]}`)},
				},
			}},
		},
		"ref-workflow": {
			input: nil,
			app: &v1beta1.Application{
				ObjectMeta: v1.ObjectMeta{
					Namespace: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Workflow: &v1beta1.Workflow{
						Ref: "ref-wf",
					},
				},
			},
			output: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name: "manual-approve",
					Type: "suspend",
				},
			}, {
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name: "deploy",
					Type: "deploy",
				},
			}},
		},
		"ref-workflow-conflict": {
			input: []workflowv1alpha1.WorkflowStep{{
				WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
					Name: "deploy",
					Type: "deploy",
				},
			}},
			app: &v1beta1.Application{
				ObjectMeta: v1.ObjectMeta{
					Namespace: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Workflow: &v1beta1.Workflow{
						Ref: "ref-wf",
						Steps: []workflowv1alpha1.WorkflowStep{{
							WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{
								Name: "deploy",
								Type: "deploy",
							},
						}},
					},
				},
			},
			hasError: true,
		},
		"ref-workflow-not-found": {
			input: nil,
			app: &v1beta1.Application{
				ObjectMeta: v1.ObjectMeta{
					Namespace: "test",
				},
				Spec: v1beta1.ApplicationSpec{
					Workflow: &v1beta1.Workflow{
						Ref: "ref-wf-404",
					},
				},
			},
			hasError: true,
		},
	}
	generator := NewChainWorkflowStepGenerator(
		&RefWorkflowStepGenerator{Context: context.Background(), Client: cli},
		&DeployWorkflowStepGenerator{},
		&Deploy2EnvWorkflowStepGenerator{},
		&ApplyComponentWorkflowStepGenerator{},
		&DeployPreApproveWorkflowStepGenerator{},
	)
	for name, testCase := range testCases {
		t.Run(name, func(t *testing.T) {
			output, err := generator.Generate(testCase.app, testCase.input)
			if testCase.hasError {
				r.Error(err)
			} else {
				r.NoError(err)
				r.Equal(testCase.output, output)
			}
		})
	}
}
