package alicloud

import (
	"context"
	"testing"

	alivpcv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/vpc/v1alpha1"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"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/controllers/vpcpeering/task"
)

// fakeVPCPeeringTask creates a fake VPCPeeringConnection task for testing
func fakeVPCPeeringTask(t *testing.T, objects ...client.Object) *VPCPeeringConnection {
	scheme := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(scheme))
	require.NoError(t, alivpcv1alpha1.AddToScheme(scheme))

	fakeClient := fake.NewClientBuilder().
		WithScheme(scheme).
		WithObjects(objects...).
		WithStatusSubresource(&alivpcv1alpha1.RouteEntry{}).
		Build()
	vpcPeering := &v1alpha1.VPCPeering{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-peering",
			Namespace: "default",
		},
		Spec: v1alpha1.VPCPeeringSpec{
			Region: "cn-hangzhou",
			Target: v1alpha1.Target{
				AliCloud: &v1alpha1.AliCloud{
					UID:      "123456789012",
					VpcID:    "vpc-test-peer",
					PeerCIDR: "10.1.0.0/16",
					Region:   "cn-shanghai",
				},
			},
		},
	}

	return &VPCPeeringConnection{
		BaseTask: &task.BaseTask{
			Context: &task.Context{
				Local:      fakeClient,
				VPCPeering: vpcPeering,
				Internet: &v1alpha1.Internet{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-internet",
						Namespace: "default",
					},
					Status: v1alpha1.InternetStatus{
						RouteTables: []v1alpha1.RouteTable{
							{Name: "rtb-nodes-cn-hangzhou-a", RouteTableID: "vtb-123"},
							{Name: "rtb-nodes-cn-hangzhou-b", RouteTableID: "vtb-456"},
						},
					},
				},
				VPC: &v1alpha1.VPC{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-vpc",
						Namespace: "default",
					},
					Status: v1alpha1.VPCStatus{
						VPC: v1alpha1.VPCRes{
							VPCID: "vpc-test-source",
						},
					},
				},
				Dataplane: &v1alpha1.DataPlane{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-dataplane",
						Namespace: "default",
					},
					Spec: v1alpha1.DataPlaneSpec{
						AccountRef: v1alpha1.AccountReference{
							ProviderConfigName: "alicloud-provider-config",
						},
					},
				},
			},
		},
		manager: NewAliCloudRouteManager(fakeClient, vpcPeering, ""),
	}
}

func TestBuildPeeringRoute(t *testing.T) {
	tests := []struct {
		name           string
		routeTableName string
		peerCIDR       string
		pcxName        string
		labels         map[string]string
		validate       func(*testing.T, *alivpcv1alpha1.RouteEntry)
	}{
		{
			name:           "valid route creation",
			routeTableName: "rtb-nodes-cn-hangzhou-a",
			peerCIDR:       "10.1.0.0/16",
			pcxName:        "pcx-12345",
			labels:         map[string]string{"test": "label"},
			validate: func(t *testing.T, route *alivpcv1alpha1.RouteEntry) {
				assert.NotNil(t, route)
				assert.Equal(t, "10.1.0.0/16", *route.Spec.ForProvider.DestinationCidrblock)
				assert.Equal(t, "pcx-12345", *route.Spec.ForProvider.NexthopID)
				assert.Equal(t, "VpcPeer", *route.Spec.ForProvider.NexthopType)
				assert.Equal(t, "rtb-nodes-cn-hangzhou-a", route.Spec.ForProvider.RouteTableIDRef.Name)
				assert.Contains(t, route.Labels, "test")
			},
		},
		{
			name:           "route with empty route table name",
			routeTableName: "",
			peerCIDR:       "10.1.0.0/16",
			pcxName:        "pcx-12345",
			labels:         map[string]string{},
			validate: func(t *testing.T, route *alivpcv1alpha1.RouteEntry) {
				// Should still create route, validation happens at apply time
				assert.NotNil(t, route)
				assert.Equal(t, "", route.Spec.ForProvider.RouteTableIDRef.Name)
			},
		},
		{
			name:           "route with multiple labels",
			routeTableName: "rtb-nodes-cn-hangzhou-a",
			peerCIDR:       "10.1.0.0/16",
			pcxName:        "pcx-12345",
			labels: map[string]string{
				"label1": "value1",
				"label2": "value2",
			},
			validate: func(t *testing.T, route *alivpcv1alpha1.RouteEntry) {
				assert.Contains(t, route.Labels, "label1")
				assert.Contains(t, route.Labels, "label2")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			task := fakeVPCPeeringTask(t)
			route := task.buildPeeringRoute(tt.routeTableName, tt.peerCIDR, tt.pcxName, "", tt.labels)
			if tt.validate != nil {
				tt.validate(t, route)
			}
		})
	}
}

func TestListPeeringRoutes(t *testing.T) {
	tests := []struct {
		name          string
		setupObjects  func() []client.Object
		expectedCount int
		expectError   bool
	}{
		{
			name: "list routes with matching labels",
			setupObjects: func() []client.Object {
				return []client.Object{
					&alivpcv1alpha1.RouteEntry{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "route-1",
							Namespace: "default",
							Labels: map[string]string{
								v1alpha1.VPCPeeringNameKey: "test-peering",
							},
						},
					},
					&alivpcv1alpha1.RouteEntry{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "route-2",
							Namespace: "default",
							Labels: map[string]string{
								v1alpha1.VPCPeeringNameKey: "test-peering",
							},
						},
					},
					&alivpcv1alpha1.RouteEntry{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "route-3",
							Namespace: "default",
							Labels: map[string]string{
								v1alpha1.VPCPeeringNameKey: "other-peering",
							},
						},
					},
				}
			},
			expectedCount: 2,
			expectError:   false,
		},
		{
			name: "no routes found",
			setupObjects: func() []client.Object {
				return []client.Object{}
			},
			expectedCount: 0,
			expectError:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			objects := tt.setupObjects()
			task := fakeVPCPeeringTask(t, objects...)

			routes, err := task.listPeeringRoutes(context.Background())
			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Len(t, routes, tt.expectedCount)
			}
		})
	}
}

func TestDeletePeeringRoutes(t *testing.T) {
	tests := []struct {
		name         string
		setupObjects func() []client.Object
		expectError  bool
		validateFunc func(*testing.T, *VPCPeeringConnection)
	}{
		{
			name: "delete existing routes",
			setupObjects: func() []client.Object {
				return []client.Object{
					&alivpcv1alpha1.RouteEntry{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "route-1",
							Namespace: "default",
							Labels: map[string]string{
								v1alpha1.VPCPeeringNameKey: "test-peering",
							},
						},
					},
					&alivpcv1alpha1.RouteEntry{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "route-2",
							Namespace: "default",
							Labels: map[string]string{
								v1alpha1.VPCPeeringNameKey: "test-peering",
							},
						},
					},
				}
			},
			expectError: false,
			validateFunc: func(t *testing.T, task *VPCPeeringConnection) {
				// Note: fake client may not fully simulate Kubernetes deletion behavior
				// (particularly with finalizers and management policies).
				// The deletion logic is tested through DeleteUpboundResource which is
				// tested elsewhere. Here we just verify the operation completes without error.
				//
				// In a real Kubernetes cluster, objects would be properly deleted.
			},
		},
		{
			name: "delete when no routes exist",
			setupObjects: func() []client.Object {
				return []client.Object{}
			},
			expectError: false,
			validateFunc: func(t *testing.T, task *VPCPeeringConnection) {
				routes, err := task.listPeeringRoutes(context.Background())
				require.NoError(t, err)
				assert.Len(t, routes, 0)
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			objects := tt.setupObjects()
			task := fakeVPCPeeringTask(t, objects...)

			err := task.deletePeeringRoutes(context.Background())
			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				if tt.validateFunc != nil {
					tt.validateFunc(t, task)
				}
			}
		})
	}
}
