package aks

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/loadbalancer/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	corev1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	maxServiceNameLength = 32

	aksPublicLoadBalancerName  = "kubernetes"
	aksPrivateLoadBalancerName = "kubernetes-internal"

	nodeResourceGroupSuffix = "-node-rg"
	aksNameLimit            = 40
)

// Service create a service for the LB
type Service struct {
	*task.BaseTask
}

func NewService(ctx *task.Context) deploy.Task {
	return &Service{
		BaseTask: &task.BaseTask{Context: ctx},
	}
}

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

func (t *Service) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	remoteCli, err := t.GetRemoteClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	svc := t.BuildBasicService()
	if t.LoadBalancer.Spec.Type == v1alpha1.PublicLoadBalancerType {
		t.overlayPublicSvc(svc)
	} else {
		t.overlayPrivateSvc(svc)
	}
	return deploy.PanicOrDone(k8s.Apply(ctx, remoteCli, svc))
}

func (t *Service) PostCreate(ctx context.Context) deploy.Result {
	remoteCli, err := t.GetRemoteClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}

	svc := t.BuildBasicService()
	if err := remoteCli.Get(ctx, client.ObjectKeyFromObject(svc), svc); err != nil {
		return deploy.Panic(fmt.Errorf("failed to get service %s/%s: %w", svc.Namespace, svc.Name, err))
	}
	if len(svc.Status.LoadBalancer.Ingress) == 0 {
		return deploy.Panic(fmt.Errorf("service %s/%s has no ingress", svc.Namespace, svc.Name))
	}

	address := svc.Status.LoadBalancer.Ingress[0].Hostname
	if address == "" {
		address = svc.Status.LoadBalancer.Ingress[0].IP
	}
	resourceID := t.getAKSLoadBalancerResourceID(string(svc.GetUID()))

	t.LoadBalancer.Status.PrivateIPs = []string{address}
	t.LoadBalancer.Status.CloudResourceId = resourceID
	return deploy.Done()
}

func (t *Service) Delete(ctx context.Context) deploy.Result {
	remoteCli, err := t.GetRemoteClient(ctx)
	if err != nil {
		return deploy.Panic(err)
	}
	svc := t.BuildServiceMeta(t.LoadBalancer)
	if err := remoteCli.Get(ctx, client.ObjectKeyFromObject(svc), svc); err != nil {
		if client.IgnoreNotFound(err) != nil {
			return deploy.Panic(fmt.Errorf("get service %s: %w", svc.Name, err))
		}
		return deploy.Done()
	}
	return deploy.PanicOrDone(k8s.DeleteObject(ctx, remoteCli, svc))
}

func (t *Service) overlayPrivateSvc(svc *corev1.Service) {
	svc.Labels[consts.TagKeyComponent] = consts.PrivateLoadBalancer
	// Use internal load balancer
	// See https://learn.microsoft.com/en-us/azure/aks/internal-lb?tabs=set-service-annotations#create-an-internal-load-balancer
	svc.Annotations["service.beta.kubernetes.io/azure-load-balancer-internal"] = "true"
	// Caller-provided annotations win: merged last so they override the
	// controller defaults above.
	for k, v := range t.LoadBalancer.Spec.Annotations {
		svc.Annotations[k] = v
	}
}

func (t *Service) overlayPublicSvc(svc *corev1.Service) {
	svc.Labels[consts.TagKeyComponent] = consts.LoadBalancer
	// Caller-provided annotations pass through unchanged.
	for k, v := range t.LoadBalancer.Spec.Annotations {
		svc.Annotations[k] = v
	}
}

func (t *Service) getAKSLoadBalancerResourceID(svcUID string) string {
	// Azure will add a character 'a' in the head and remove all the '-' to construct load balancer name, add 'a' and then limit the length to 32
	// https://github.com/kubernetes-sigs/cloud-provider-azure/blob/06ec1846c13c3ea6b366a5b888d0fff6a28a77a2/pkg/provider/azure_loadbalancer.go#L4144
	frontendIPConfigurationID := strings.ReplaceAll("a"+svcUID, "-", "")
	if len(frontendIPConfigurationID) > maxServiceNameLength {
		frontendIPConfigurationID = frontendIPConfigurationID[:maxServiceNameLength]
	}

	subscriptionID := t.VPC.Spec.AccountRef.AccountID
	resourceGroup := t.getNodeResourceGroupName()
	// Name of the load balancer, when using internal load balancer, the name is kubernetes-internal
	loadBalancerName := aksPublicLoadBalancerName
	if t.LoadBalancer.Spec.Type == v1alpha1.PrivateLoadBalancerType {
		loadBalancerName = aksPrivateLoadBalancerName
	}

	return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/frontendIPConfigurations/%s", subscriptionID, resourceGroup, loadBalancerName, frontendIPConfigurationID)
}

func (t *Service) getNodeResourceGroupName() string {
	aksName := namer.UniqueIdentifier(t.AppBox.ObjectMeta, namer.WithLimit(aksNameLimit))
	return fmt.Sprintf("%s%s", aksName, nodeResourceGroupSuffix)
}
