package common

import (
	"fmt"
	"os"
	"testing"

	. "github.com/onsi/gomega"
	"github.com/samber/lo"
	"github.com/stretchr/testify/assert"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/yaml"

	storagev1 "k8s.io/api/storage/v1"

	"github.com/tidbcloud/eks-provider/controllers/cluster/task"
	"github.com/tidbcloud/eks-provider/controllers/cluster/task/common/audit"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

func TestStorageClass_getClusterStorageClass_Label(t *testing.T) {
	type fields struct {
		BaseTask *task.BaseTask
	}
	tests := []struct {
		name    string
		fields  fields
		want    string
		wantErr bool
	}{
		{
			name: "aws-label",
			fields: fields{
				BaseTask: &task.BaseTask{
					Context: &task.Context{
						Cluster: &cloudv1.Cluster{
							ObjectMeta: metav1.ObjectMeta{
								Labels: map[string]string{
									cloudv1.ProviderTypeLabel: cloudv1.ProviderTypeEKS,
									cloudv1.TenantTagKey:      "1234567",
									cloudv1.ProjectTagKey:     "1234567",
								},
							},
							Spec: cloudv1.ClusterSpec{
								Cluster: cloudv1.ClusterTopology{
									PD: cloudv1.PD{
										StorageClassName: "pd-gp3-8000-300",
									},
									TiKV: cloudv1.TiKV{
										StorageClassName: "tikv-gp3-3000-144",
									},
								},
							},
						},
					},
				},
			},
			want: "testdata/storageclass/storageclass-aws-label.yaml",
		},
		{
			name: "gcp-label",
			fields: fields{
				BaseTask: &task.BaseTask{
					Context: &task.Context{
						Cluster: &cloudv1.Cluster{
							ObjectMeta: metav1.ObjectMeta{
								Labels: map[string]string{
									cloudv1.ProviderTypeLabel: cloudv1.ProviderTypeGKE,
									cloudv1.TenantTagKey:      "1234567",
									cloudv1.ProjectTagKey:     "1234567",
								},
							},
							Spec: cloudv1.ClusterSpec{
								Cluster: cloudv1.ClusterTopology{
									PD: cloudv1.PD{
										StorageClassName: "pd-pd-ssd",
									},
									TiKV: cloudv1.TiKV{
										StorageClassName: "tikv-pd-ssd",
									},
								},
							},
						},
					},
				},
			},
			want: "testdata/storageclass/storageclass-gcp-label.yaml",
		},
		{
			name: "azure-label",
			fields: fields{
				BaseTask: &task.BaseTask{
					Context: &task.Context{
						Cluster: &cloudv1.Cluster{
							ObjectMeta: metav1.ObjectMeta{
								Labels: map[string]string{
									cloudv1.ProviderTypeLabel: cloudv1.ProviderTypeAKS,
									cloudv1.TenantTagKey:      "1234567",
									cloudv1.ProjectTagKey:     "1234567",
								},
							},
							Spec: cloudv1.ClusterSpec{
								Cluster: cloudv1.ClusterTopology{
									PD: cloudv1.PD{
										StorageClassName: "pd-premiumv2-8000-300",
									},
									TiKV: cloudv1.TiKV{
										StorageClassName: "tikv-premiumv2-3000-144",
									},
								},
							},
						},
					},
				},
			},
			want: "testdata/storageclass/storageclass-azure-label.yaml",
		},
		{
			name: "gcp-n4-label",
			fields: fields{
				BaseTask: &task.BaseTask{
					Context: &task.Context{
						Cluster: &cloudv1.Cluster{
							ObjectMeta: metav1.ObjectMeta{
								Labels: map[string]string{
									cloudv1.ProviderTypeLabel: cloudv1.ProviderTypeGKE,
									cloudv1.TenantTagKey:      "1234567",
									cloudv1.ProjectTagKey:     "1234567",
								},
							},
							Spec: cloudv1.ClusterSpec{
								Cluster: cloudv1.ClusterTopology{
									PD: cloudv1.PD{
										StorageClassName: "pd-hyperdisk-balanced-3000-100",
									},
									TiKV: cloudv1.TiKV{
										StorageClassName: "tikv-hyperdisk-balanced-2000-50",
									},
								},
							},
						},
					},
				},
			},
			want: "testdata/storageclass/storageclass-gcp-n4-label.yaml",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewWithT(t)
			tr := &StorageClass{
				BaseTask: tt.fields.BaseTask,
			}
			got, err := tr.getClusterStorageClass()
			if (err != nil) != tt.wantErr {
				t.Errorf("StorageClass.getClusterStorageClass() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			gotYAML, marshalErr := yaml.Marshal(got)
			if marshalErr != nil {
				t.Errorf("Failed to marshal got: %v", marshalErr)
			}
			fmt.Println(string(gotYAML))

			// // generate the expected YAML file
			// err = os.WriteFile(tt.want, gotYAML, 0644)
			// g.Expect(err).ToNot(HaveOccurred(), "Failed to write generated YAML to file")
			// t.Logf("Generated YAML written to %s", tt.want)

			// Compare the generated YAML with the expected YAML
			expectedYAML, err := os.ReadFile(tt.want)
			g.Expect(err).ToNot(HaveOccurred(), "Failed to read expected YAML file")
			t.Log(string(expectedYAML))
			g.Expect(string(gotYAML)).To(Equal(string(expectedYAML)))
		})
	}
}

func TestStorageClass_getStaticStorageClass(t *testing.T) {
	type fields struct {
		BaseTask *task.BaseTask
	}
	tests := []struct {
		name    string
		fields  fields
		want    []*storagev1.StorageClass
		wantErr bool
	}{
		{
			name: "get static storage class",
			fields: fields{
				BaseTask: &task.BaseTask{
					Context: &task.Context{
						Cluster: &cloudv1.Cluster{
							ObjectMeta: metav1.ObjectMeta{
								Labels: map[string]string{
									cloudv1.TenantTagKey:      "test-tenant",
									cloudv1.ProjectTagKey:     "test-project",
									cloudv1.ProviderTypeLabel: cloudv1.ProviderTypeEKS,
								},
							},
						},
						Provider: &mockProvider{
							storageClasses: []*storagev1.StorageClass{
								{
									ObjectMeta: metav1.ObjectMeta{
										Name: "gp2",
									},
									Provisioner: "kubernetes.io/aws-ebs",
									Parameters: map[string]string{
										"type": "gp2",
									},
								},
								{
									ObjectMeta: metav1.ObjectMeta{
										Name: audit.StorageClassName,
									},
									Provisioner: "kubernetes.io/aws-ebs",
									Parameters: map[string]string{
										"type": "gp2",
									},
								},
							},
						},
					},
				},
			},
			want: []*storagev1.StorageClass{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "gp2",
					},
					Provisioner: "kubernetes.io/aws-ebs",
					Parameters: map[string]string{
						"type":               "gp2",
						"tagSpecification_1": "servicetype=dedicated",
						"tagSpecification_2": "tenant=test-tenant",
						"tagSpecification_3": "project=test-project",
						"tagSpecification_4": "usedby=dbaas-control-plane-shoot",
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: audit.StorageClassName,
					},
					Provisioner: "kubernetes.io/aws-ebs",
					Parameters: map[string]string{
						"type":               "gp2",
						"tagSpecification_1": "servicetype=dedicated",
						"tagSpecification_2": "tenant=test-tenant",
						"tagSpecification_3": "project=test-project",
						"tagSpecification_4": "usedby=customer-tidb",
					},
				},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := &StorageClass{
				BaseTask: tt.fields.BaseTask,
			}
			got, err := tr.getStaticStorageClass()
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
			for idx, want := range tt.want {
				assert.Equal(t, want.Name, got[idx].Name)
				// compare only the parameters' values ignore order
				assert.ElementsMatchf(t, lo.Values(want.Parameters), lo.Values(got[idx].Parameters), "storage class %s", want.Name)
			}
		})
	}
}

type mockProvider struct {
	storageClasses []*storagev1.StorageClass
}

func (m *mockProvider) GetProviderType() string {
	return "aws"
}

func (m *mockProvider) GetInstanceTypes() ([]cloudv1.InstanceType, error) {
	return nil, nil
}

func (m *mockProvider) GetStorageClasses() ([]*storagev1.StorageClass, error) {
	return m.storageClasses, nil
}

func (m *mockProvider) FindInstanceType(name string) (cloudv1.InstanceType, error) {
	return cloudv1.InstanceType{}, nil
}

func (m *mockProvider) FindRegionZones(region string) ([]string, error) {
	return nil, nil
}
