package aks

import (
	"context"
	"fmt"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	azurenetworkv1beta1 "github.com/upbound/provider-azure/apis/network/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"

	"github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/azure"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/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/util"
)

type apiServerPrivateLinkService struct {
	*task.BaseTask
	azureCli azure.Client
}

func NewAPIServerPrivateLinkService(ctx *task.Context, azureCli azure.Client) deploy.Task {
	return &apiServerPrivateLinkService{
		BaseTask: &task.BaseTask{Context: ctx},
		azureCli: azureCli,
	}
}

func (t *apiServerPrivateLinkService) Name() string {
	return "APIServerPrivateLinkService"
}

func (t *apiServerPrivateLinkService) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	if len(t.VPC.Status.PLSNATSubnets) == 0 {
		return deploy.Panic(fmt.Errorf("pls nat subnet is not ready")).WithRequeuAfter(defaultRequeuInterval)
	}

	lbFrontendID, err := t.apiServerLoadBalancerFrontendID(ctx)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}

	tags := controllers.ConvertToCloudTags(t.APPBox.Labels, consts.ProviderTypeAKS)
	tags = util.MergeMaps(tags, t.Config.AdditionalCloudTags)

	pls := &azurenetworkv1beta1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.resourceName(),
			Labels: util.MergeMaps(t.Namer.Labels(), t.APPBox.Labels),
		},
		Spec: azurenetworkv1beta1.PrivateLinkServiceSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: t.ProviderConfigName()},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
			ForProvider: azurenetworkv1beta1.PrivateLinkServiceParameters{
				Location:                               ptr.To(t.APPBox.Spec.Region),
				ResourceGroupName:                      ptr.To(controllers.GetResourceGroupName(t.VPC, t.Config.ResourceNamePrefix)),
				LoadBalancerFrontendIPConfigurationIds: []*string{ptr.To(lbFrontendID)},
				NATIPConfiguration:                     buildAppBoxNATIPConfigurations(t.VPC.Status.PLSNATSubnets),
				Tags:                                   util.ToMapPointer(tags),
			},
		},
	}

	return deploy.PanicOrDone(k8s.Apply(ctx, t.Local, pls))
}

func (t *apiServerPrivateLinkService) PostCreate(ctx context.Context) deploy.Result {
	pls := &azurenetworkv1beta1.PrivateLinkService{}
	if err := t.Local.Get(ctx, types.NamespacedName{Name: t.resourceName()}, pls); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	if !controllers.IsResourceReady(pls.Status.ResourceStatus) {
		return deploy.Panic(fmt.Errorf("api server private link service is not ready")).WithRequeuAfter(defaultRequeuInterval)
	}
	if ptr.Deref(pls.Status.AtProvider.Alias, "") == "" {
		return deploy.Panic(fmt.Errorf("api server private link service alias is empty")).WithRequeuAfter(defaultRequeuInterval)
	}
	return deploy.Done()
}

func (t *apiServerPrivateLinkService) Delete(ctx context.Context) deploy.Result {
	return deploy.PanicOrDone(controllers.DeleteUpboundResource(ctx, t.Local, &azurenetworkv1beta1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{Name: t.resourceName()},
	}))
}

func (t *apiServerPrivateLinkService) PostDelete(ctx context.Context) deploy.Result {
	return deploy.PanicOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.resourceName()}, &azurenetworkv1beta1.PrivateLinkService{}))
}

func (t *apiServerPrivateLinkService) resourceName() string {
	return aksClusterName(t.APPBox) + apiServerPrivateLinkServiceSuffix
}

func (t *apiServerPrivateLinkService) apiServerLoadBalancerFrontendID(ctx context.Context) (string, error) {
	loadBalancers, err := t.azureCli.LoadBalancers(t.ProviderConfigName())
	if err != nil {
		return "", err
	}
	resp, err := loadBalancers.Get(ctx, aksNodeResourceGroupName(t.APPBox), "kube-apiserver")
	if err != nil {
		return "", err
	}
	if resp.Properties == nil || len(resp.Properties.FrontendIPConfigurations) == 0 || resp.Properties.FrontendIPConfigurations[0] == nil {
		return "", fmt.Errorf("kube-apiserver load balancer has no frontend ip configuration")
	}
	return ptr.Deref(resp.Properties.FrontendIPConfigurations[0].ID, ""), nil
}
