package eks

import (
	"context"
	"testing"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/stretchr/testify/assert"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"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"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	"github.com/upbound/provider-aws/apis/ec2/v1beta1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
)

func fakeSubnetTask(t *testing.T) (*Subnet, *VPC) {
	c := fakeTaskContext(t)
	c.Config = config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			RegionConfig: config.RegionConfig{
				Name:  "us-west-2",
				Zones: []string{"us-west-2a", "us-west-2b", "us-west-2c"},
			},
		},
		ResourceNamePrefix: "tidbcloud",
	}
	vpcTask := NewVPC(c)
	subnetTask := NewSubnet(c)
	return subnetTask, vpcTask
}

func TestSubnet_Create(t *testing.T) {
	t.Run("get vpcRes error", func(t *testing.T) {
		subnetTask, _ := fakeSubnetTask(t)
		result := subnetTask.Create(context.Background())
		res, err, retriableErr := deploy.MinRequeue([]deploy.Result{result})
		assert.Nil(t, err)
		assert.ErrorContains(t, retriableErr, "not found")
		assert.Equal(t, time.Second*30, res.RequeueAfter)
	})
	t.Run("create subnet success", func(t *testing.T) {
		subnetTask, vpcTask := fakeSubnetTask(t)
		result := vpcTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()
		result = subnetTask.Create(context.Background())
		assert.True(t, result.IsDoneState())
		vpcName := subnetTask.getVPCName()
		zones := vpcTask.GetZones()
		workers, public, private, err := util.GetAwsSubnetCIDRs(subnetTask.VPC.Spec.CIDR, len(zones))
		assert.Nil(t, err)
		for idx, nodeSubnet := range subnetTask.nodeSubnets {
			assert.Equal(t, nodeSubnet.Name, namer.NodesSubnetName(vpcName, zones[idx]))
			assert.Equal(t, nodeSubnet.Spec.ForProvider.CidrBlock, pointer.String(workers[idx]))
			assert.Equal(t, nodeSubnet.Spec.ForProvider.AvailabilityZone, pointer.String(zones[idx]))
			assert.Equal(t, nodeSubnet.Spec.ForProvider.VPCIDRef.Name, vpcName)
		}

		for idx, publicSubnet := range subnetTask.publicSubnets {
			assert.Equal(t, publicSubnet.Name, namer.PublicSubnetName(vpcName, zones[idx]))
			assert.Equal(t, publicSubnet.Spec.ForProvider.CidrBlock, pointer.String(public[idx]))
			assert.Equal(t, publicSubnet.Spec.ForProvider.AvailabilityZone, pointer.String(zones[idx]))
			assert.Equal(t, publicSubnet.Spec.ForProvider.VPCIDRef.Name, vpcName)
		}

		for idx, privateSubnet := range subnetTask.privateSubnets {
			assert.Equal(t, privateSubnet.Name, namer.PrivateSubnetName(vpcName, zones[idx]))
			assert.Equal(t, privateSubnet.Spec.ForProvider.CidrBlock, pointer.String(private[idx]))
			assert.Equal(t, privateSubnet.Spec.ForProvider.AvailabilityZone, pointer.String(zones[idx]))
			assert.Equal(t, privateSubnet.Spec.ForProvider.VPCIDRef.Name, vpcName)
		}
	})
}

func TestSubnet_CreateOptimized(t *testing.T) {
	subnetTask, vpcTask := fakeSubnetTask(t)
	subnetTask.VPC.Spec.Zones = []string{"us-west-2a", "us-west-2b", "us-west-2c"}
	subnetTask.VPC.Spec.NodeZones = []string{"us-west-2a", "us-west-2b"}

	result := vpcTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()

	result = subnetTask.Create(context.Background())
	assert.True(t, result.IsDoneState())

	vpcName := subnetTask.getVPCName()
	zones := subnetTask.GetZones()
	workers, public, private, controlPlanes, err := util.GetAwsSubnetCIDRsOptimized(
		subnetTask.VPC.Spec.CIDR,
		subnetTask.VPC.Spec.NodeZones,
		zones,
	)
	assert.NoError(t, err)

	assert.Len(t, subnetTask.nodeSubnets, len(subnetTask.VPC.Spec.NodeZones))
	assert.Len(t, subnetTask.publicSubnets, len(subnetTask.VPC.Spec.NodeZones))
	assert.Len(t, subnetTask.privateSubnets, len(subnetTask.VPC.Spec.NodeZones))
	assert.Len(t, subnetTask.controlPlaneSubnets, 1)

	for _, nodeSubnet := range subnetTask.nodeSubnets {
		zone := *nodeSubnet.Spec.ForProvider.AvailabilityZone
		assert.Equal(t, nodeSubnet.Name, namer.NodesSubnetName(vpcName, zone))
		assert.Equal(t, nodeSubnet.Spec.ForProvider.CidrBlock, pointer.String(workers[zone]))
	}
	for _, publicSubnet := range subnetTask.publicSubnets {
		zone := *publicSubnet.Spec.ForProvider.AvailabilityZone
		assert.Equal(t, publicSubnet.Name, namer.PublicSubnetName(vpcName, zone))
		assert.Equal(t, publicSubnet.Spec.ForProvider.CidrBlock, pointer.String(public[zone]))
	}
	for _, privateSubnet := range subnetTask.privateSubnets {
		zone := *privateSubnet.Spec.ForProvider.AvailabilityZone
		assert.Equal(t, privateSubnet.Name, namer.PrivateSubnetName(vpcName, zone))
		assert.Equal(t, privateSubnet.Spec.ForProvider.CidrBlock, pointer.String(private[zone]))
	}
	for _, controlPlaneSubnet := range subnetTask.controlPlaneSubnets {
		zone := *controlPlaneSubnet.Spec.ForProvider.AvailabilityZone
		assert.Equal(t, controlPlaneSubnet.Name, namer.ControlPlaneSubnetName(vpcName, zone))
		assert.Equal(t, controlPlaneSubnet.Spec.ForProvider.CidrBlock, pointer.String(controlPlanes[zone]))
	}

	subnetTask.VPC.Status.ControlPlaneSubnets = []v1alpha1.Subnet{
		{
			Name:             subnetTask.controlPlaneSubnets[0].Name,
			AvailabilityZone: *subnetTask.controlPlaneSubnets[0].Spec.ForProvider.AvailabilityZone,
		},
	}

	result = subnetTask.Delete(context.Background())
	assert.True(t, result.IsDoneState())
	for _, controlPlaneSubnet := range subnetTask.VPC.Status.ControlPlaneSubnets {
		err := subnetTask.Local.Get(context.Background(), types.NamespacedName{Name: controlPlaneSubnet.Name}, &v1beta1.Subnet{})
		assert.ErrorContains(t, err, "not found")
	}
}

func TestSubnet_CreateOptimizedWithPrivateLinkEndpointSubnets(t *testing.T) {
	subnetTask, vpcTask := fakeSubnetTask(t)
	subnetTask.VPC.Spec.PrivateLinkEndpointZones = []string{
		"us-west-2f",
		"us-west-2a",
		"us-west-2c",
		"us-west-2b",
		"us-west-2e",
		"us-west-2d",
	}
	subnetTask.VPC.Spec.CIDR = "10.0.0.0/18"
	subnetTask.VPC.Spec.Zones = []string{"us-west-2a", "us-west-2b", "us-west-2c"}
	subnetTask.VPC.Spec.NodeZones = []string{"us-west-2a"}

	result := vpcTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()

	result = subnetTask.Create(context.Background())
	assert.True(t, result.IsDoneState())

	vpcName := subnetTask.getVPCName()
	endpoints, err := util.GetAwsPrivateLinkEndpointSubnetCIDRs(
		subnetTask.VPC.Spec.CIDR,
		subnetTask.VPC.Spec.PrivateLinkEndpointZones,
	)
	assert.NoError(t, err)
	assert.Len(t, subnetTask.endpointSubnets, 6)

	for _, endpointSubnet := range subnetTask.endpointSubnets {
		zone := *endpointSubnet.Spec.ForProvider.AvailabilityZone
		assert.Equal(t, namer.PrivateLinkEndpointSubnetName(vpcName, zone), endpointSubnet.Name)
		assert.Equal(t, pointer.String(endpoints[zone]), endpointSubnet.Spec.ForProvider.CidrBlock)
		assert.Equal(t, pointer.String(zone), endpointSubnet.Spec.ForProvider.AvailabilityZone)
		assert.Nil(t, endpointSubnet.Spec.ForProvider.AvailabilityZoneID)
		assert.Equal(t, vpcName, endpointSubnet.Spec.ForProvider.VPCIDRef.Name)
	}
}

func TestSubnet_CreateOptimizedWithPrivateLinkEndpointZonesSkipsMultiNodeZones(t *testing.T) {
	subnetTask, vpcTask := fakeSubnetTask(t)
	subnetTask.VPC.Spec.PrivateLinkEndpointZones = []string{
		"us-west-2a",
		"us-west-2b",
		"us-west-2c",
	}
	subnetTask.VPC.Spec.CIDR = "10.0.0.0/18"
	subnetTask.VPC.Spec.Zones = []string{"us-west-2a", "us-west-2b", "us-west-2c"}
	subnetTask.VPC.Spec.NodeZones = []string{"us-west-2a", "us-west-2b"}

	result := vpcTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()

	result = subnetTask.Create(context.Background())
	assert.True(t, result.IsDoneState())

	assert.Len(t, subnetTask.nodeSubnets, 2)
	assert.Len(t, subnetTask.publicSubnets, 2)
	assert.Len(t, subnetTask.privateSubnets, 2)
	assert.Len(t, subnetTask.controlPlaneSubnets, 1)
	assert.Empty(t, subnetTask.endpointSubnets)
}

func TestSubnet_CreateWithPrivateLinkEndpointZonesReportsOptimizedCIDRInputs(t *testing.T) {
	subnetTask, vpcTask := fakeSubnetTask(t)
	subnetTask.VPC.Spec.PrivateLinkEndpointZones = []string{"us-west-2a"}
	subnetTask.VPC.Spec.CIDR = "10.0.0.0/18"
	subnetTask.VPC.Spec.Zones = []string{"us-west-2a"}
	subnetTask.VPC.Spec.NodeZones = []string{"us-west-2a"}

	result := vpcTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()

	result = subnetTask.Create(context.Background())
	assert.ErrorContains(t, result.Error(), "vpc.spec.privateLinkEndpointZones requires optimized CIDR layout")
	assert.ErrorContains(t, result.Error(), "nodeZones=[us-west-2a]")
	assert.ErrorContains(t, result.Error(), "zones=[us-west-2a]")
}

func TestSubnet_createNodeSubnet(t *testing.T) {
	subnetTask, vpcTask := fakeSubnetTask(t)
	result := vpcTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()
	vpcName := subnetTask.getVPCName()
	key := types.NamespacedName{
		Name: vpcName,
	}
	vpcRes := &v1beta1.VPC{}
	err := subnetTask.Local.Get(context.Background(), key, vpcRes)
	assert.Nil(t, err)
	tags := cloudv1.TagsFromLabels(vpcTask.VPC.Labels)
	cidr := "10.16.0.0/21"
	zone := "us-west-2a"
	t.Run("create node subnet success", func(t *testing.T) {
		subnet, err := subnetTask.createNodeSubnet(context.Background(), zone, cidr, vpcRes, tags)
		assert.Nil(t, err)
		assert.Equal(t, v1beta1.SubnetSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: subnetTask.ProviderConfigName()},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
			ForProvider: v1beta1.SubnetParameters_2{
				Region:           vpcRes.Spec.ForProvider.Region,
				CidrBlock:        pointer.String(cidr),
				AvailabilityZone: pointer.String(zone),
				Tags:             util.ToMapPointer(util.BuildAWSTagWithName(tags, namer.NodesSubnetName(vpcName, "us-west-2a"))),
				VPCIDRef: &xpv1.Reference{
					Name: vpcName,
				},
			},
		}, subnet.Spec)
	})
}

func TestSubnet_createPublicSubnet(t *testing.T) {
	subnetTask, vpcTask := fakeSubnetTask(t)
	result := vpcTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()
	vpcName := subnetTask.getVPCName()
	key := types.NamespacedName{
		Name: vpcName,
	}
	vpcRes := &v1beta1.VPC{}
	err := subnetTask.Local.Get(context.Background(), key, vpcRes)
	assert.Nil(t, err)
	tags := cloudv1.TagsFromLabels(vpcTask.VPC.Labels)
	cidr := "10.16.0.0/21"
	zone := "us-west-2a"
	t.Run("create public subnet success", func(t *testing.T) {
		subnet, err := subnetTask.createPublicSubnet(context.Background(), zone, cidr, vpcRes, tags)
		assert.Nil(t, err)
		tags := util.BuildAWSTagWithName(tags, namer.PublicSubnetName(vpcName, zone))
		tagsetting := util.ToMapPointer(tags)

		assert.Equal(t, v1beta1.SubnetSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: subnetTask.ProviderConfigName()},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
			ForProvider: v1beta1.SubnetParameters_2{
				Region:           vpcRes.Spec.ForProvider.Region,
				CidrBlock:        pointer.String(cidr),
				Tags:             tagsetting,
				AvailabilityZone: pointer.String(zone),
				VPCIDRef: &xpv1.Reference{
					Name: vpcName,
				},
			},
		}, subnet.Spec)
	})
}

func TestSubnet_createPrivateSubnet(t *testing.T) {
	subnetTask, vpcTask := fakeSubnetTask(t)
	result := vpcTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()
	vpcName := subnetTask.getVPCName()
	key := types.NamespacedName{
		Name: vpcName,
	}
	vpcRes := &v1beta1.VPC{}
	err := subnetTask.Local.Get(context.Background(), key, vpcRes)
	assert.Nil(t, err)
	tags := cloudv1.TagsFromLabels(vpcTask.VPC.Labels)
	cidr := "10.16.0.0/21"
	zone := "us-west-2a"
	t.Run("create private subnet success", func(t *testing.T) {
		subnet, err := subnetTask.createPrivateSubnet(context.Background(), zone, cidr, vpcRes, tags)
		assert.Nil(t, err)
		tags := util.BuildAWSTagWithName(tags, namer.PrivateSubnetName(vpcName, zone))
		tagsetting := util.ToMapPointer(tags)
		assert.Equal(t, v1beta1.SubnetSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: subnetTask.ProviderConfigName()},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
			ForProvider: v1beta1.SubnetParameters_2{
				Region:           vpcRes.Spec.ForProvider.Region,
				CidrBlock:        pointer.String(cidr),
				Tags:             tagsetting,
				AvailabilityZone: pointer.String(zone),
				VPCIDRef: &xpv1.Reference{
					Name: vpcName,
				},
			},
		}, subnet.Spec)
	})
}

func TestSubnet_CreateOrUpdateRefreshesTags(t *testing.T) {
	tests := []struct {
		name   string
		create func(*Subnet, context.Context, *v1beta1.VPC, map[string]string) (*v1beta1.Subnet, error)
	}{
		{
			name: "node subnet",
			create: func(task *Subnet, ctx context.Context, vpc *v1beta1.VPC, tags map[string]string) (*v1beta1.Subnet, error) {
				return task.createNodeSubnet(ctx, "us-west-2a", "10.16.0.0/21", vpc, tags)
			},
		},
		{
			name: "public subnet",
			create: func(task *Subnet, ctx context.Context, vpc *v1beta1.VPC, tags map[string]string) (*v1beta1.Subnet, error) {
				return task.createPublicSubnet(ctx, "us-west-2a", "10.16.8.0/21", vpc, tags)
			},
		},
		{
			name: "private subnet",
			create: func(task *Subnet, ctx context.Context, vpc *v1beta1.VPC, tags map[string]string) (*v1beta1.Subnet, error) {
				return task.createPrivateSubnet(ctx, "us-west-2a", "10.16.16.0/21", vpc, tags)
			},
		},
		{
			name: "control plane subnet",
			create: func(task *Subnet, ctx context.Context, vpc *v1beta1.VPC, tags map[string]string) (*v1beta1.Subnet, error) {
				return task.createControlPlaneSubnet(ctx, "us-west-2a", "10.16.24.0/24", vpc, tags)
			},
		},
		{
			name: "private link endpoint subnet",
			create: func(task *Subnet, ctx context.Context, vpc *v1beta1.VPC, tags map[string]string) (*v1beta1.Subnet, error) {
				return task.createPrivateLinkEndpointSubnet(ctx, "us-west-2a", "10.16.25.0/24", vpc, tags)
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			subnetTask, vpcTask := fakeSubnetTask(t)
			subnetTask.VPC.Labels = util.MergeMaps(subnetTask.VPC.Labels, map[string]string{
				"tags.tidbcloud.com/cp9-validation-revision": "v1",
			})

			result := vpcTask.Create(ctx)
			assert.True(t, result.IsDoneState())
			subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()
			vpcRes := &v1beta1.VPC{}
			err := subnetTask.Local.Get(ctx, types.NamespacedName{Name: subnetTask.getVPCName()}, vpcRes)
			assert.NoError(t, err)

			initialTags := controllers.ConvertToCloudTags(subnetTask.VPC.Labels, cloudv1.ProviderTypeEKS)
			subnet, err := tt.create(subnetTask, ctx, vpcRes, initialTags)
			assert.NoError(t, err)
			subnet.Spec.ForProvider.Tags["kubernetes.io/cluster/test"] = pointer.String("owned")
			assert.NoError(t, subnetTask.Local.Update(ctx, subnet))

			subnetTask.VPC.Labels["tags.tidbcloud.com/cp9-validation-revision"] = "v2"
			subnetTask.VPC.Labels["tags.tidbcloud.com/cp9-validation-added"] = "yes"
			updatedTags := controllers.ConvertToCloudTags(subnetTask.VPC.Labels, cloudv1.ProviderTypeEKS)
			_, err = tt.create(subnetTask, ctx, vpcRes, updatedTags)
			assert.NoError(t, err)

			got := &v1beta1.Subnet{}
			assert.NoError(t, subnetTask.Local.Get(ctx, types.NamespacedName{Name: subnet.Name}, got))
			assert.Equal(t, pointer.String("v2"), got.Spec.ForProvider.Tags["cp9-validation-revision"])
			assert.Equal(t, pointer.String("yes"), got.Spec.ForProvider.Tags["cp9-validation-added"])
			assert.Equal(t, pointer.String("owned"), got.Spec.ForProvider.Tags["kubernetes.io/cluster/test"])
			assert.Equal(t, "v2", got.Labels["tags.tidbcloud.com/cp9-validation-revision"])
			assert.Equal(t, "yes", got.Labels["tags.tidbcloud.com/cp9-validation-added"])
		})
	}
}

func TestSubnet_Delete(t *testing.T) {
	subnetTask, vpcTask := fakeSubnetTask(t)
	result := vpcTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	subnetTask.VPC.Status.VPC.Name = vpcTask.getVPCName()
	result = subnetTask.Create(context.Background())
	assert.True(t, result.IsDoneState())
	zones := vpcTask.GetZones()
	t.Run("delete subnet success", func(t *testing.T) {
		result := subnetTask.Delete(context.Background())
		assert.True(t, result.IsDoneState())
		vpcName := subnetTask.getVPCName()
		for _, zone := range zones {
			key := types.NamespacedName{
				Name: namer.NodesSubnetName(vpcName, zone),
			}
			subnet := &v1beta1.Subnet{}
			err := subnetTask.Local.Get(context.Background(), key, subnet)
			assert.ErrorContains(t, err, "not found")

			key = types.NamespacedName{
				Name: namer.PublicSubnetName(vpcName, zone),
			}
			subnet = &v1beta1.Subnet{}
			err = subnetTask.Local.Get(context.Background(), key, subnet)
			assert.ErrorContains(t, err, "not found")

			key = types.NamespacedName{
				Name: namer.PrivateSubnetName(vpcName, zone),
			}
			err = subnetTask.Local.Get(context.Background(), key, subnet)
			assert.ErrorContains(t, err, "not found")
		}
	})

	t.Run("delete subnet by label selector when status is empty", func(t *testing.T) {
		subnetTask2, vpcTask2 := fakeSubnetTask(t)
		result2 := vpcTask2.Create(context.Background())
		assert.True(t, result2.IsDoneState())
		subnetTask2.VPC.Status.VPC.Name = vpcTask2.getVPCName()
		result2 = subnetTask2.Create(context.Background())
		assert.True(t, result2.IsDoneState())

		vpcName2 := subnetTask2.getVPCName()
		zones2 := vpcTask2.GetZones()

		subnetTask2.VPC.Status.NodeSubnets = nil
		subnetTask2.VPC.Status.PublicSubnets = nil
		subnetTask2.VPC.Status.PrivateSubnets = nil
		subnetTask2.VPC.Status.ControlPlaneSubnets = nil

		result2 = subnetTask2.Delete(context.Background())
		assert.True(t, result2.IsDoneState())

		for _, zone := range zones2 {
			key := types.NamespacedName{Name: namer.NodesSubnetName(vpcName2, zone)}
			subnet := &v1beta1.Subnet{}
			err := subnetTask2.Local.Get(context.Background(), key, subnet)
			assert.ErrorContains(t, err, "not found")

			key = types.NamespacedName{Name: namer.PublicSubnetName(vpcName2, zone)}
			err = subnetTask2.Local.Get(context.Background(), key, subnet)
			assert.ErrorContains(t, err, "not found")

			key = types.NamespacedName{Name: namer.PrivateSubnetName(vpcName2, zone)}
			err = subnetTask2.Local.Get(context.Background(), key, subnet)
			assert.ErrorContains(t, err, "not found")
		}
	})
}
