package aks

import (
	"context"
	"fmt"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	cloudv1 "github.com/tidbcloud/infra-api/api/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/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkservice/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"
)

const (
	defaultRequeuInterval = time.Second * 30
)

type PrivateLinkService struct {
	*task.BaseTask
}

func NewPrivateLinkService(c *task.Context) *PrivateLinkService {
	return &PrivateLinkService{BaseTask: &task.BaseTask{Context: c}}
}

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

func getPrivateLinkServiceResourceName(privatelinkserivce *v1alpha1.PrivateLinkService) string {
	return privatelinkserivce.Name
}

func (t *PrivateLinkService) Create(ctx context.Context) deploy.Result {
	if len(t.VPC.Status.PLSNATSubnets) < 1 {
		return deploy.Panic(fmt.Errorf("no plsNAT subnet found")).WithRequeuAfter(defaultRequeuInterval)
	}
	natConfigs := []v1beta1.NATIPConfigurationParameters{}
	for idx, sn := range t.VPC.Status.PLSNATSubnets {
		config := v1beta1.NATIPConfigurationParameters{
			Name: ptr.To(sn.Name),
			SubnetIDRef: ptr.To(xpv1.Reference{
				Name: sn.Name,
			}),
		}
		if idx == 0 {
			config.Primary = ptr.To(true)
		}
		natConfigs = append(natConfigs, config)
	}
	if len(t.PrivateLinkService.Status.LoadBalancers) < 1 {
		return deploy.Panic(fmt.Errorf("no load balancer found")).WithRequeuAfter(defaultRequeuInterval)
	}
	lbs := []*string{}
	for _, lb := range t.PrivateLinkService.Status.LoadBalancers {
		lbs = append(lbs, ptr.To(lb.CloudResourceId))
	}
	tags := cloudv1.TagsFromLabels(t.PrivateLinkService.Labels)
	tags = util.AddResourceName(tags, t.PrivateLinkService.Name)
	ves := &v1beta1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{
			Name: getPrivateLinkServiceResourceName(t.PrivateLinkService),
			Labels: map[string]string{
				v1alpha1.PrivateLinkServiceNameKey: t.PrivateLinkService.Name,
				v1alpha1.VPCNameKey:                t.VPC.Name,
			},
		},
		Spec: v1beta1.PrivateLinkServiceSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
			},
			ForProvider: v1beta1.PrivateLinkServiceParameters{
				LoadBalancerFrontendIPConfigurationIds: lbs,
				ResourceGroupNameRef: &xpv1.Reference{
					Name: controllers.GetResourceGroupName(t.VPC),
				},
				Location:           ptr.To(t.PrivateLinkService.Spec.Region),
				NATIPConfiguration: natConfigs,
				Tags:               util.ToMapPointer(tags),
			},
		},
	}
	if err := k8s.Apply(ctx, t.Local, ves); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	return deploy.Done()
}

func (t *PrivateLinkService) PostCreate(ctx context.Context) deploy.Result {
	ves := &v1beta1.PrivateLinkService{}
	if err := t.Local.Get(ctx, types.NamespacedName{Name: getPrivateLinkServiceResourceName(t.PrivateLinkService)}, ves); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeuInterval)
	}
	condition := ves.Status.GetCondition(xpv1.TypeReady)
	if condition.Status != corev1.ConditionTrue {
		return deploy.Panic(fmt.Errorf("private link service is not ready")).WithRequeuAfter(defaultRequeuInterval)
	}

	t.PrivateLinkService.Status.AzurePrivateLinkService = v1alpha1.AzurePrivateLinkServiceStatus{
		Name:           meta.GetExternalName(ves),
		ID:             ptr.Deref(ves.Status.AtProvider.ID, ""),
		Alias:          ptr.Deref(ves.Status.AtProvider.Alias, ""),
		ServiceDNSName: fmt.Sprintf("%s.%s", t.PrivateLinkService.Status.DNSIdentify, t.Config.PublicHostedZoneDomain),
	}

	return deploy.PanicOrDone(t.PrivateLinkServiceRW.UpdateStatus(ctx, t.PrivateLinkService))
}

func (t *PrivateLinkService) Delete(ctx context.Context) deploy.Result {
	ves := &v1beta1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{
			Name: getPrivateLinkServiceResourceName(t.PrivateLinkService),
		},
	}
	return deploy.ErrorOrDone(k8s.DeleteObject(ctx, t.Local, ves))
}

func (t *PrivateLinkService) PostDelete(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: getPrivateLinkServiceResourceName(t.PrivateLinkService)}, &v1beta1.PrivateLinkService{}))
}
