package webhooks

import (
	"context"
	"encoding/json"
	"path"
	"testing"

	"github.com/stretchr/testify/assert"
	config2 "github.com/tidbcloud/config-manager/pkg/config"
	admissionv1 "k8s.io/api/admission/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/record"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	sdk "github.com/tidbcloud/infra-provider/pkg/gitops-sdk"
	"github.com/tidbcloud/infra-provider/pkg/scheme"
)

func TestClusterRevisionMutator_handleUpdate(t *testing.T) {
	// so ugly
	config2.InitConfig(path.Join("../../../cluster-controller", "examples/eks-provider/root-config-eks-dev.yaml"))
	cr1 := &cloudv1.ConfigRevision{
		ObjectMeta: metav1.ObjectMeta{
			Name: "cr1",
		},
		Spec: cloudv1.ConfigRevisionSpec{
			TiDB: &cloudv1.Configuration{
				Config: cloudv1.NewTomlConfig(map[string]interface{}{
					"a": "1",
				}),
			},
			Version: "v7.5.1",
		},
	}
	cr2 := &cloudv1.ConfigRevision{
		ObjectMeta: metav1.ObjectMeta{
			Name: "cr2",
		},
		Spec: cloudv1.ConfigRevisionSpec{
			TiDB: &cloudv1.Configuration{
				Config: cloudv1.NewTomlConfig(map[string]interface{}{
					"a": "2",
				}),
			},
			Version: "v7.5.1",
		},
	}
	cluster := &cloudv1.Cluster{
		Spec: cloudv1.ClusterSpec{
			NetworkName:       "eks-us-west-2",
			ConfigRevisionRef: &cloudv1.Reference{Name: cr1.Name},
			Cluster: cloudv1.ClusterTopology{
				TiDB: cloudv1.TiDB{
					InstanceType: "m5.2xlarge",
					Replicas:     1,
				},
			},
		},
	}
	type args struct {
		clusterMutator            func() (newCluster, oldCluster *cloudv1.Cluster)
		reCalculateConfigRevision func(infraCli client.Client, c *cloudv1.Cluster, instanceChanged []cloudv1.ComponentType) (*cloudv1.ConfigRevision, error)
	}
	tests := []struct {
		name         string
		args         args
		want         admission.Response
		generateWant func(newCluster, oldCluster *cloudv1.Cluster) admission.Response
	}{
		{
			name: "no instance changed",
			args: args{
				clusterMutator: func() (newCluster, oldCluster *cloudv1.Cluster) {
					return cluster.DeepCopy(), cluster.DeepCopy()
				},
			},
			want: admission.ValidationResponse(true, "no instance changed"),
		}, {
			name: "change instance type of tidb",
			args: args{
				clusterMutator: func() (newCluster, oldCluster *cloudv1.Cluster) {
					newCluster = cluster.DeepCopy()
					oldCluster = cluster.DeepCopy()
					newCluster.Spec.Cluster.TiDB.InstanceType = "m5.4xlarge"

					return newCluster, oldCluster
				},
				reCalculateConfigRevision: func(infraCli client.Client, c *cloudv1.Cluster, instanceChanged []cloudv1.ComponentType) (*cloudv1.ConfigRevision, error) {
					return cr2, nil
				},
			},
			want: admission.PatchResponseFromRaw(nil, nil),
			generateWant: func(newCluster, _ *cloudv1.Cluster) admission.Response {
				cloned := newCluster.DeepCopy()
				original, _ := json.Marshal(cloned)
				cloned.Spec.ConfigRevisionRef.Name = cr2.Name
				current, _ := json.Marshal(cloned)

				return admission.PatchResponseFromRaw(original, current)
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			cli := fake.NewClientBuilder().
				WithScheme(scheme.Scheme).
				WithObjects(cr1).
				Build()
			recorder := record.NewFakeRecorder(10240)
			decoder := admission.NewDecoder(scheme.Scheme)
			c := &ClusterRevisionMutator{
				cli:           cli,
				eventRecorder: recorder,
				g:             nil, // not used
				rv:            nil, // not used
				decoder:       decoder,
			}

			newCluster, oldCluster := tt.args.clusterMutator()
			clusterBytes, _ := json.Marshal(newCluster)
			oldClusterBytes, _ := json.Marshal(oldCluster)
			if tt.generateWant != nil {
				tt.want = tt.generateWant(newCluster, oldCluster)
			}

			if tt.args.reCalculateConfigRevision != nil {
				sdk.ReCalculateConfigRevision = tt.args.reCalculateConfigRevision
			} else {
				sdk.ReCalculateConfigRevision = func(infraCli client.Client, c *cloudv1.Cluster, instanceChanged []cloudv1.ComponentType) (*cloudv1.ConfigRevision, error) {
					// don't change ConfigRevision by default
					return cr1, nil
				}
			}

			req := admission.Request{
				AdmissionRequest: admissionv1.AdmissionRequest{
					Operation: admissionv1.Update,
					Object: runtime.RawExtension{
						Raw: clusterBytes,
					},
					OldObject: runtime.RawExtension{
						Raw:    oldClusterBytes,
						Object: nil,
					},
				},
			}

			got := c.handleUpdate(ctx, req)
			assert.Equal(t, tt.want, got, "result")
		})
	}
}
