package operate

import (
	"testing"

	v1 "github.com/tidbcloud/infra-component/api/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestValidateComponent(t *testing.T) {
	testCases := []struct {
		name      string
		component v1.Component
		wantValid bool
	}{
		{
			name:      "empty spec component",
			wantValid: true,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec:       v1.ComponentSpec{Version: "v1.0.0"},
			},
		},
		{
			name:      "component with no name",
			wantValid: false,
			component: v1.Component{
				Spec: v1.ComponentSpec{Version: "v1.0.0"},
			},
		},
		{
			name:      "invalid version",
			wantValid: false,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec:       v1.ComponentSpec{Version: "release-1.0"},
			},
		},
		{
			name:      "valid selectors",
			wantValid: true,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Selector: v1.ComponentSelector{
						NetworkSelector: []v1.MatchingExpression{
							{Key: "labels", Operator: "include", Value: "tags.tidbcloud.com"},
						},
						ClusterSelector: []v1.MatchingExpression{
							{Key: "version", Operator: "greater", Value: "v1.0.0"},
						},
					},
				},
			},
		},
		{
			name:      "matching expression has no key",
			wantValid: false,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Selector: v1.ComponentSelector{
						NetworkSelector: []v1.MatchingExpression{
							{Key: "", Operator: "include", Value: "tags.tidbcloud.com"},
						},
					},
				},
			},
		},
		{
			name:      "invalid operator in the network selector",
			wantValid: false,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Selector: v1.ComponentSelector{
						NetworkSelector: []v1.MatchingExpression{
							{Key: "labels", Operator: "exist", Value: "tags.tidbcloud.com"},
						},
					},
				},
			},
		},
		{
			name:      "invalid operator in the cluster selector",
			wantValid: false,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Selector: v1.ComponentSelector{
						ClusterSelector: []v1.MatchingExpression{
							{Key: "labels", Operator: "exist", Value: "tags.tidbcloud.com"},
						},
					},
				},
			},
		},
		{
			name:      "object has no name",
			wantValid: false,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Objects: []v1.ComponentObject{
						{
							DeletePolicy: "delete",
						},
					},
				},
			},
		},
		{
			name:      "invalid object delete policy",
			wantValid: false,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Objects: []v1.ComponentObject{
						{
							Name:         "test",
							DeletePolicy: "invalid",
						},
					},
				},
			},
		},
		{
			name:      "empty object template",
			wantValid: false,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Objects: []v1.ComponentObject{
						{
							Name:         "test",
							DeletePolicy: "delete",
							Template:     "",
						},
					},
				},
			},
		},
		{
			name:      "invalid object template",
			wantValid: false,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Objects: []v1.ComponentObject{
						{
							Name:         "test",
							DeletePolicy: "delete",
							Template:     "{{ !Data }}",
						},
					},
				},
			},
		},
		{
			name:      "component with valid object",
			wantValid: true,
			component: v1.Component{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSpec{
					Version: "v1.0.0",
					Objects: []v1.ComponentObject{
						{
							Name:         "test",
							DeletePolicy: "delete",
							Template:     "{{ .Data }}",
						},
					},
				},
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			err := ValidateComponent(tc.component)
			valid := err == nil
			if valid != tc.wantValid {
				t.Fatalf("ValidateComponent valid got = %v, want = %v", valid, tc.wantValid)
			}
		})
	}
}

func TestValidateComponentSet(t *testing.T) {
	testCases := []struct {
		name         string
		componentset v1.ComponentSet
		wantValid    bool
	}{
		{
			name:         "component set has no name",
			wantValid:    false,
			componentset: v1.ComponentSet{},
		},
		{
			name:      "component set has invalid component spec",
			wantValid: false,
			componentset: v1.ComponentSet{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSetSpec{
					Versions: []v1.ComponentSpec{
						{},
					},
				},
			},
		},
		{
			name:      "component set has duplicate versions",
			wantValid: false,
			componentset: v1.ComponentSet{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSetSpec{
					Versions: []v1.ComponentSpec{
						{Version: "v1.0.0"},
						{Version: "v1.0.0"},
					},
				},
			},
		},
		{
			name:      "valid component set",
			wantValid: true,
			componentset: v1.ComponentSet{
				ObjectMeta: metav1.ObjectMeta{Name: "test"},
				Spec: v1.ComponentSetSpec{
					Versions: []v1.ComponentSpec{
						{Version: "v1.0.0"},
						{Version: "v1.1.0"},
					},
				},
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			err := ValidateComponentSet(tc.componentset)
			valid := err == nil
			if valid != tc.wantValid {
				t.Fatalf("ValidateComponentSet valid got = %v, want = %v", valid, tc.wantValid)
			}
		})
	}
}
