package ack

import (
	"context"
	"testing"

	ackv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/ack/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/go-logr/logr"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
)

func buildTestScheme() *runtime.Scheme {
	scheme := runtime.NewScheme()
	_ = ackv1alpha1.SchemeBuilder.AddToScheme(scheme)
	_ = v1alpha1.SchemeBuilder.AddToScheme(scheme)
	return scheme
}

func TestControlPlaneVswitches(t *testing.T) {
	testCases := []struct {
		name               string
		existingCluster    *ackv1alpha1.ManagedKubernetes
		publicSubnets      []v1alpha1.Subnet
		nodeSubnets        []v1alpha1.Subnet
		expectedVswitches  []string
		expectError        bool
		expectedLogMessage string
	}{
		{
			name:            "new cluster uses public vswitches",
			existingCluster: nil,
			publicSubnets: []v1alpha1.Subnet{
				{SubnetID: "vsw-public-1", Name: "public-1"},
				{SubnetID: "vsw-public-2", Name: "public-2"},
			},
			nodeSubnets: []v1alpha1.Subnet{
				{SubnetID: "vsw-node-1", Name: "node-1"},
				{SubnetID: "vsw-node-2", Name: "node-2"},
			},
			expectedVswitches:  []string{"vsw-public-1", "vsw-public-2"},
			expectedLogMessage: "Creating new ACK cluster with public vswitches for control plane",
		},
		{
			name: "existing cluster preserves node vswitches",
			existingCluster: &ackv1alpha1.ManagedKubernetes{
				ObjectMeta: metav1.ObjectMeta{
					Name: "", // Will be set dynamically based on K8sName()
				},
				Spec: ackv1alpha1.ManagedKubernetesSpec{
					ForProvider: ackv1alpha1.ManagedKubernetesParameters{
						VswitchIds: []*string{
							ptr.To("vsw-node-1"),
							ptr.To("vsw-node-2"),
						},
					},
				},
			},
			publicSubnets: []v1alpha1.Subnet{
				{SubnetID: "vsw-public-1", Name: "public-1"},
				{SubnetID: "vsw-public-2", Name: "public-2"},
			},
			nodeSubnets: []v1alpha1.Subnet{
				{SubnetID: "vsw-node-1", Name: "node-1"},
				{SubnetID: "vsw-node-2", Name: "node-2"},
			},
			expectedVswitches:  []string{"vsw-node-1", "vsw-node-2"},
			expectedLogMessage: "ACK cluster exists, preserving existing vswitch configuration",
		},
		{
			name: "existing cluster preserves public vswitches",
			existingCluster: &ackv1alpha1.ManagedKubernetes{
				ObjectMeta: metav1.ObjectMeta{
					Name: "", // Will be set dynamically based on K8sName()
				},
				Spec: ackv1alpha1.ManagedKubernetesSpec{
					ForProvider: ackv1alpha1.ManagedKubernetesParameters{
						VswitchIds: []*string{
							ptr.To("vsw-public-1"),
							ptr.To("vsw-public-2"),
						},
					},
				},
			},
			publicSubnets: []v1alpha1.Subnet{
				{SubnetID: "vsw-public-1", Name: "public-1"},
				{SubnetID: "vsw-public-2", Name: "public-2"},
			},
			nodeSubnets: []v1alpha1.Subnet{
				{SubnetID: "vsw-node-1", Name: "node-1"},
				{SubnetID: "vsw-node-2", Name: "node-2"},
			},
			expectedVswitches:  []string{"vsw-public-1", "vsw-public-2"},
			expectedLogMessage: "ACK cluster exists, preserving existing vswitch configuration",
		},
		{
			name:            "new cluster with no public subnets returns error",
			existingCluster: nil,
			publicSubnets:   []v1alpha1.Subnet{},
			nodeSubnets: []v1alpha1.Subnet{
				{SubnetID: "vsw-node-1", Name: "node-1"},
			},
			expectError: true,
		},
		{
			name:            "new cluster with empty public subnet ID returns error",
			existingCluster: nil,
			publicSubnets: []v1alpha1.Subnet{
				{SubnetID: "", Name: "public-1"},
			},
			nodeSubnets: []v1alpha1.Subnet{
				{SubnetID: "vsw-node-1", Name: "node-1"},
			},
			expectError: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			g := NewWithT(t)
			ctx := context.Background()

			// Create task context first to determine K8sName
			taskCtx := &task.Context{
				Logger: logr.Discard(),
				APPBox: &v1alpha1.APPBox{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-appbox",
						Namespace: "default",
					},
					Spec: v1alpha1.APPBoxSpec{
						Region: "ap-southeast-1",
					},
				},
				VPC: &v1alpha1.VPC{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-vpc",
						Namespace: "default",
					},
					Status: v1alpha1.VPCStatus{
						PublicSubnets: tc.publicSubnets,
						NodeSubnets:   tc.nodeSubnets,
					},
				},
				Config: config.RootConfig{
					ResourceNamePrefix: "test",
				},
			}
			taskCtx.Namer = namer.NewNamer().
				LegacyWithNetworkName(taskCtx.VPC.Name).
				LegacyWithNetworkNamespace(taskCtx.VPC.Namespace).
				WithAPPBoxName(taskCtx.APPBox.Name)

			// Create ACK task to get K8sName
			ackTask := &ack{
				BaseTask: &task.BaseTask{
					Context: taskCtx,
				},
			}

			// Setup fake client with proper cluster name
			var objects []client.Object
			if tc.existingCluster != nil {
				tc.existingCluster.Name = ackTask.K8sName()
				objects = append(objects, tc.existingCluster)
			}
			cli := fake.NewClientBuilder().
				WithScheme(buildTestScheme()).
				WithObjects(objects...).
				Build()

			// Update task context with client
			taskCtx.Local = cli
			ackTask.Local = cli

			// Execute
			vswitches, err := ackTask.controlPlaneVswitches(ctx)

			// Verify
			if tc.expectError {
				g.Expect(err).To(HaveOccurred())
				return
			}

			g.Expect(err).NotTo(HaveOccurred())
			g.Expect(vswitches).To(Equal(tc.expectedVswitches))
		})
	}
}

func TestControlPlaneVswitches_Integration(t *testing.T) {
	g := NewWithT(t)
	ctx := context.Background()

	// Setup fake client
	cli := fake.NewClientBuilder().WithScheme(buildTestScheme()).Build()

	publicSubnets := []v1alpha1.Subnet{
		{SubnetID: "vsw-public-1", Name: "public-1"},
		{SubnetID: "vsw-public-2", Name: "public-2"},
	}
	nodeSubnets := []v1alpha1.Subnet{
		{SubnetID: "vsw-node-1", Name: "node-1"},
		{SubnetID: "vsw-node-2", Name: "node-2"},
	}

	// Create task context
	taskCtx := &task.Context{
		Local:  cli,
		Logger: logr.Discard(),
		APPBox: &v1alpha1.APPBox{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test-appbox",
				Namespace: "default",
			},
			Spec: v1alpha1.APPBoxSpec{
				Region: "ap-southeast-1",
			},
		},
		VPC: &v1alpha1.VPC{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "test-vpc",
				Namespace: "default",
			},
			Status: v1alpha1.VPCStatus{
				PublicSubnets: publicSubnets,
				NodeSubnets:   nodeSubnets,
			},
		},
		Config: config.RootConfig{
			ResourceNamePrefix: "test",
		},
	}
	taskCtx.Namer = namer.NewNamer().
		LegacyWithNetworkName(taskCtx.VPC.Name).
		LegacyWithNetworkNamespace(taskCtx.VPC.Namespace).
		WithAPPBoxName(taskCtx.APPBox.Name)

	ackTask := &ack{
		BaseTask: &task.BaseTask{
			Context: taskCtx,
		},
	}

	// First call - no cluster exists, should return public vswitches
	vswitches1, err := ackTask.controlPlaneVswitches(ctx)
	g.Expect(err).NotTo(HaveOccurred())
	g.Expect(vswitches1).To(Equal([]string{"vsw-public-1", "vsw-public-2"}))

	// Create a cluster with node vswitches (simulating what existed before)
	cluster := &ackv1alpha1.ManagedKubernetes{
		ObjectMeta: metav1.ObjectMeta{
			Name: ackTask.K8sName(),
		},
		Spec: ackv1alpha1.ManagedKubernetesSpec{
			ForProvider: ackv1alpha1.ManagedKubernetesParameters{
				VswitchIds: []*string{
					ptr.To("vsw-node-1"),
					ptr.To("vsw-node-2"),
				},
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: "test-provider"},
			},
		},
	}
	err = cli.Create(ctx, cluster)
	g.Expect(err).NotTo(HaveOccurred())

	// Second call - cluster now exists, should preserve existing vswitches
	vswitches2, err := ackTask.controlPlaneVswitches(ctx)
	g.Expect(err).NotTo(HaveOccurred())
	g.Expect(vswitches2).To(Equal([]string{"vsw-node-1", "vsw-node-2"}))
}
