package aks

import (
	"context"
	"fmt"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/cidr"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpc/task"
	"github.com/upbound/provider-azure/apis/network/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type Subnet struct {
	*task.BaseTask
}

func NewSubnet(ctx *task.Context) *Subnet {
	return &Subnet{&task.BaseTask{Context: ctx}}
}

var _ deploy.Task = &Subnet{}

// Name implements Task interface.
func (t *Subnet) Name() string {
	return "Subnet"
}

// Create implements Task interface.
// Note: return deploy.Panic once Subnet is not set up correctly.
func (t *Subnet) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	cidrDispatcher, err := cidr.NewAKSCIDRDispatcher(t.VPC.Spec.CIDR)
	if err != nil {
		return deploy.Panic(err)
	}
	if err = cidrDispatcher.Allocate(); err != nil {
		return deploy.Panic(err)
	}

	// create subnet for AKS pod
	if err := t.createSubnet(ctx, t.getPodSubnetName(), cidrDispatcher.PodIPv4CIDRBlock(), v1alpha1.PodSubnetType, true); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	// create subnet for AKS node
	if err := t.createSubnet(ctx, t.getNodeSubnetName(), cidrDispatcher.NodeIPv4CIDRBlock(), v1alpha1.NodeSubnetType, true); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	// create subnet for PrivateLink Service NAT
	// Subnet for PLS NAT should not have PrivateLinkServiceNetworkPolicies disabled
	// https://learn.microsoft.com/en-us/azure/private-link/disable-private-link-service-network-policy?tabs=private-link-network-policy-powershell
	if err := t.createSubnet(ctx, t.getPLSSubnetName(), cidrDispatcher.PLSNATIPv4CIDRBlock(), v1alpha1.PLSNATSubnetType, false); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	if err := t.createSubnet(
		ctx,
		t.getAPIServerSubnetName(),
		cidrDispatcher.APIServerIPv4CIDRBlock(),
		v1alpha1.APIServerSubnetType,
		true,
		[]v1beta1.DelegationParameters{
			{
				Name: ptr.To("aks-apiserver"),
				ServiceDelegation: []v1beta1.ServiceDelegationParameters{
					{
						Name: ptr.To("Microsoft.ContainerService/managedClusters"),
					},
				},
			},
		},
	); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}

	return deploy.Done()
}

func (t *Subnet) getPodSubnetName() string {
	return fmt.Sprintf("%s-pod", getVNetName(t.VPC))
}

func (t *Subnet) getNodeSubnetName() string {
	return fmt.Sprintf("%s-node", getVNetName(t.VPC))
}

func (t *Subnet) getPLSSubnetName() string {
	return fmt.Sprintf("%s-pls", getVNetName(t.VPC))
}

func (t *Subnet) getAPIServerSubnetName() string {
	return fmt.Sprintf("%s-apiserver", getVNetName(t.VPC))
}

func (t *Subnet) createSubnet(
	ctx context.Context,
	name, cidr string,
	subnetType string,
	privateLinkServiceNetworkPoliciesEnabled bool,
	delegation ...[]v1beta1.DelegationParameters,
) error {
	rgName := controllers.GetResourceGroupName(t.VPC)
	vnetName := getVNetName(t.VPC)
	labels := map[string]string{
		v1alpha1.VPCNameKey:    t.VPC.Name,
		v1alpha1.SubnetTypeKey: subnetType,
	}
	subnet := &v1beta1.Subnet{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	_, err := controllerutil.CreateOrUpdate(ctx, t.Local, subnet, func() error {
		subnet.Labels = labels
		subnet.Spec = v1beta1.SubnetSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
			},
			ForProvider: v1beta1.SubnetParameters{
				AddressPrefixes:                          []*string{ptr.To(cidr)},
				ResourceGroupNameRef:                     &xpv1.Reference{Name: rgName},
				VirtualNetworkNameRef:                    &xpv1.Reference{Name: vnetName},
				PrivateLinkServiceNetworkPoliciesEnabled: ptr.To(privateLinkServiceNetworkPoliciesEnabled),
			},
		}
		if len(delegation) > 0 {
			subnet.Spec.ForProvider.Delegation = delegation[0]
		}
		return nil
	})
	return err
}

// PostCreate implements Task interface.
func (t *Subnet) PostCreate(ctx context.Context) deploy.Result {
	// check pod subnet
	podSubnetStatus, podSubnetReady, err := t.checkSubnet(t.getPodSubnetName())
	if err != nil || !podSubnetReady {
		return deploy.Panic(fmt.Errorf("pod subnet %s is not ready", t.getPodSubnetName())).WithRequeuAfter(defaultRequeuInterval)
	}
	t.VPC.Status.PodSubnets = []v1alpha1.Subnet{podSubnetStatus}

	// check node subnet
	nodeSubnetStatus, nodeSubnetReady, err := t.checkSubnet(t.getNodeSubnetName())
	if err != nil || !nodeSubnetReady {
		return deploy.Panic(fmt.Errorf("node subnet %s is not ready", t.getNodeSubnetName())).WithRequeuAfter(defaultRequeuInterval)
	}
	t.VPC.Status.NodeSubnets = []v1alpha1.Subnet{nodeSubnetStatus}

	// check PLS subnet
	plsSubnetStatus, plsSubnetReady, err := t.checkSubnet(t.getPLSSubnetName())
	if err != nil || !plsSubnetReady {
		return deploy.Panic(fmt.Errorf("PLS subnet %s is not ready", t.getPLSSubnetName())).WithRequeuAfter(defaultRequeuInterval)
	}
	t.VPC.Status.PLSNATSubnets = []v1alpha1.Subnet{plsSubnetStatus}

	apiServerSubnetStatus, apiServerSubnetReady, err := t.checkSubnet(t.getAPIServerSubnetName())
	if err != nil || !apiServerSubnetReady {
		return deploy.Panic(fmt.Errorf("api server subnet %s is not ready", t.getAPIServerSubnetName())).WithRequeuAfter(defaultRequeuInterval)
	}
	t.VPC.Status.ControlPlaneSubnets = []v1alpha1.Subnet{apiServerSubnetStatus}

	if err := t.VPCRW.UpdateStatus(ctx, t.VPC); err != nil {
		return deploy.Panic(err)
	}

	return deploy.Done()
}

func (t *Subnet) checkSubnet(name string) (v1alpha1.Subnet, bool, error) {
	subnet := &v1beta1.Subnet{
		ObjectMeta: metav1.ObjectMeta{Name: name},
	}
	subnetStatus := v1alpha1.Subnet{}
	if err := t.Local.Get(context.Background(), types.NamespacedName{Name: subnet.Name}, subnet); err != nil {
		return subnetStatus, false, err
	}

	condition := subnet.Status.ConditionedStatus.GetCondition(xpv1.TypeReady)
	if condition.Status != corev1.ConditionTrue {
		return subnetStatus, false, fmt.Errorf("subnet %s is not ready: %v", subnet.Name, condition.Reason)
	}

	if len(subnet.Spec.ForProvider.AddressPrefixes) == 0 {
		return subnetStatus, false, fmt.Errorf("subnet %s has no address prefixes", subnet.Name)
	}

	subnetStatus = v1alpha1.Subnet{
		Name:      subnet.Name,
		SubnetID:  ptr.Deref(subnet.Status.AtProvider.ID, ""),
		CIDRBlock: *subnet.Spec.ForProvider.AddressPrefixes[0],
	}

	return subnetStatus, true, nil
}

// Delete implements Task interface.
// Note: return deploy.Panic once Subnet is not deleted correctly.
func (t *Subnet) Delete(ctx context.Context) deploy.Result {
	// delete pls nat subnet
	if err := t.deleteSubnet(ctx, t.getPLSSubnetName()); err != nil {
		return deploy.Error(err)
	}

	if err := t.deleteSubnet(ctx, t.getAPIServerSubnetName()); err != nil {
		return deploy.Error(err)
	}

	// delete node subnet
	if err := t.deleteSubnet(ctx, t.getNodeSubnetName()); err != nil {
		return deploy.Error(err)
	}

	// delete pod subnet
	if err := t.deleteSubnet(ctx, t.getPodSubnetName()); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *Subnet) deleteSubnet(ctx context.Context, name string) error {
	subnet := &v1beta1.Subnet{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if err := k8s.DeleteObject(ctx, t.Local, subnet); err != nil {
		return err
	}
	return nil
}

// PostDelete implements Task interface.
func (t *Subnet) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}
