package eks

import (
	"context"
	"fmt"
	"net"
	"sort"
	"time"

	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
	"github.com/crossplane-contrib/provider-aws/apis/ec2/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/samber/lo"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	commoncidr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/cidr"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpc/task"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type SecondaryCidrSubnet struct {
	*task.BaseTask
}

func NewSecondaryCidrSubnet(ctx *task.Context) *SecondaryCidrSubnet {
	return &SecondaryCidrSubnet{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

func (t *SecondaryCidrSubnet) Name() string {
	return "SecondaryCidrSubnet"
}

func (t *SecondaryCidrSubnet) Create(ctx context.Context) deploy.Result {
	configZones := t.GetZones()
	sort.Strings(configZones)
	key := types.NamespacedName{
		Name: t.GetVPCName(),
	}
	tags := cloudv1.TagsFromLabels(t.VPC.Labels)
	vpcRes := &v1beta1.VPC{}
	err := t.Local.Get(ctx, key, vpcRes)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	existingSecondaryCidrSubnets := &v1beta1.SubnetList{}
	err = t.Local.List(ctx, existingSecondaryCidrSubnets, client.MatchingLabels{
		v1alpha1.VPCNameKey:    t.VPC.Name,
		v1alpha1.SubnetTypeKey: v1alpha1.SecondaryCidrNodeSubnetType,
	})
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	existingSecondaryCidrs := lo.SliceToMap(existingSecondaryCidrSubnets.Items, func(subnet v1beta1.Subnet) (string, string) {
		return subnet.Spec.ForProvider.CIDRBlock, subnet.Name
	})

	for _, cidr := range t.VPC.Spec.SecondaryCidrs {
		_, prefixNet, err := net.ParseCIDR(cidr)
		if err != nil {
			return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
		}
		subnets, err := commoncidr.SplitSubnet(prefixNet, 4)
		if err != nil {
			return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
		}
		for idx, zone := range configZones {
			subnetCidr := subnets[idx]
			secondaryCidrName, err := t.GetSecondaryCidrName(subnetCidr.String())
			subnetName := namer.NodesSubnetName(secondaryCidrName, zone)
			if err != nil {
				return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
			}
			if _, ok := existingSecondaryCidrs[subnetCidr.String()]; ok && existingSecondaryCidrs[subnetCidr.String()] != subnetName {
				return deploy.Panic(fmt.Errorf("secondary CIDR block %s is overlapped with existing secondary CIDR block %s",
					subnetCidr.String(), existingSecondaryCidrs[subnetCidr.String()])).WithRequeuAfter(time.Second * 30)
			}
			subnet := &v1beta1.Subnet{
				ObjectMeta: metav1.ObjectMeta{
					Name: subnetName,
					Labels: map[string]string{
						v1alpha1.VPCNameKey:    t.VPC.Name,
						v1alpha1.SubnetTypeKey: v1alpha1.SecondaryCidrNodeSubnetType,
					},
				},
				Spec: v1beta1.SubnetSpec{
					ForProvider: v1beta1.SubnetParameters{
						Region:           vpcRes.Spec.ForProvider.Region,
						CIDRBlock:        subnetCidr.String(),
						AvailabilityZone: ptr.To(zone),
						VPCIDRef: &xpv1.Reference{
							Name: vpcRes.Name,
						},
						Tags: util.GetEC2V1BetaTags(util.AddResourceName(tags, secondaryCidrName)),
					},
					ResourceSpec: xpv1.ResourceSpec{
						ProviderConfigReference: &xpv1.Reference{Name: t.ProviderConfigName()},
					},
				},
			}
			_, err = controllerutil.CreateOrUpdate(ctx, t.Local, subnet, func() error {
				return controllerutil.SetControllerReference(vpcRes, subnet, t.Local.Scheme())
			})
			if err != nil {
				return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
			}
		}
	}
	return deploy.Done()
}

func (t *SecondaryCidrSubnet) PostCreate(ctx context.Context) deploy.Result {
	secondaryCidrSubnets := &v1beta1.SubnetList{}
	err := t.Local.List(ctx, secondaryCidrSubnets, client.MatchingLabels{
		v1alpha1.VPCNameKey:    t.VPC.Name,
		v1alpha1.SubnetTypeKey: v1alpha1.SecondaryCidrNodeSubnetType,
	})
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	secondaryNodeSubnets := []v1alpha1.Subnet{}
	for _, subnet := range secondaryCidrSubnets.Items {
		if subnet.Status.AtProvider.SubnetState != string(ec2types.SubnetStateAvailable) {
			return deploy.Panic(fmt.Errorf("subnet status not available: %v", subnet.Status.AtProvider.SubnetState)).WithRequeuAfter(time.Second * 30)
		}
		secondaryNodeSubnets = append(secondaryNodeSubnets, v1alpha1.Subnet{
			SubnetID:           subnet.Status.AtProvider.SubnetID,
			Name:               subnet.Name,
			AvailabilityZone:   ptr.Deref(subnet.Spec.ForProvider.AvailabilityZone, ""),
			AvailabilityZoneID: ptr.Deref(subnet.Spec.ForProvider.AvailabilityZoneID, ""),
			CIDRBlock:          subnet.Spec.ForProvider.CIDRBlock,
		})
	}
	t.VPC.Status.SecondaryNodeSubnets = secondaryNodeSubnets
	if err := t.VPCRW.UpdateStatus(ctx, t.VPC); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	return deploy.Done()
}

func (t *SecondaryCidrSubnet) Delete(ctx context.Context) deploy.Result {
	secondaryCidrSubnets := &v1beta1.SubnetList{}
	err := t.Local.List(ctx, secondaryCidrSubnets, client.MatchingLabels{
		v1alpha1.VPCNameKey:    t.VPC.Name,
		v1alpha1.SubnetTypeKey: v1alpha1.SecondaryCidrNodeSubnetType,
	})
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	for _, subnet := range secondaryCidrSubnets.Items {
		err := t.Local.Delete(ctx, &subnet)
		if err != nil {
			return deploy.Error(err)
		}
	}
	return deploy.Done()
}
