package aks

import (
	"context"
	"fmt"
	"time"

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

const defaultRequeueInterval = time.Second * 30

type EndpointService struct {
	*task.BaseTask
}

func NewEndpointService(c *task.Context) deploy.Task {
	return &EndpointService{BaseTask: &task.BaseTask{Context: c}}
}

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

func (t *EndpointService) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	subnets, err := t.getPLSNATSubnets()
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeueInterval)
	}
	resourceGroup := t.VPC.Status.ResourceGroup.ID
	if resourceGroup == "" {
		return deploy.Panic(fmt.Errorf("resource group is not ready")).WithRequeuAfter(defaultRequeueInterval)
	}
	if t.PrivateLinkService.Status.LoadBalancer.CloudResourceId == "" {
		return deploy.Panic(fmt.Errorf("load balancer frontend ID is empty")).WithRequeuAfter(defaultRequeueInterval)
	}

	tags := t.EffectiveCloudTags(consts.ProviderTypeAKS)

	pls := &azurenetworkv1beta1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{
			Name:   t.EndpointServiceName(),
			Labels: t.PrivateLinkService.Labels,
		},
		Spec: azurenetworkv1beta1.PrivateLinkServiceSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
			ForProvider: azurenetworkv1beta1.PrivateLinkServiceParameters{
				Location:                               ptr.To(t.PrivateLinkService.Spec.Region),
				ResourceGroupName:                      ptr.To(resourceGroup),
				LoadBalancerFrontendIPConfigurationIds: []*string{ptr.To(t.PrivateLinkService.Status.LoadBalancer.CloudResourceId)},
				NATIPConfiguration:                     buildNATIPConfigurations(subnets),
				Tags:                                   util.ToMapPointer(tags),
			},
		},
	}

	if err := k8s.Apply(ctx, t.Local, pls); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeueInterval)
	}
	return deploy.Done()
}

func (t *EndpointService) PostCreate(ctx context.Context) deploy.Result {
	pls := &azurenetworkv1beta1.PrivateLinkService{}
	if err := t.Local.Get(ctx, types.NamespacedName{Name: t.EndpointServiceName()}, pls); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeueInterval)
	}
	if !controllers.IsResourceReady(pls.Status.ResourceStatus) {
		t.Logger.Info("PrivateLinkService is not ready", "status", pls.Status.GetCondition(xpv1.TypeReady).Status)
		return deploy.Panic(fmt.Errorf("PrivateLinkService is not ready")).WithRequeuAfter(defaultRequeueInterval)
	}

	if err := t.setPrivateLinkServiceStatus(ctx, pls); err != nil {
		return deploy.Panic(err).WithRequeuAfter(defaultRequeueInterval)
	}
	return deploy.Done()
}

func (t *EndpointService) setPrivateLinkServiceStatus(ctx context.Context, pls *azurenetworkv1beta1.PrivateLinkService) error {
	t.PrivateLinkService.Status.AzurePrivateLinkService = v1alpha1.AzurePrivateLinkServiceStatus{
		Name:           t.EndpointServiceName(),
		Alias:          ptr.Deref(pls.Status.AtProvider.Alias, ""),
		ID:             ptr.Deref(pls.Status.AtProvider.ID, ""),
		ServiceDNSName: fmt.Sprintf("%s.%s", t.PrivateLinkService.Status.DNSIdentify, t.Config.DNSProviderConfig.AzureDNSZone.Domain),
	}
	return t.PrivateLinkServiceRW.UpdateStatus(ctx, t.PrivateLinkService)
}

func (t *EndpointService) Delete(ctx context.Context) deploy.Result {
	pls := &azurenetworkv1beta1.PrivateLinkService{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.EndpointServiceName(),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, t.Local, pls); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

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

func (t *EndpointService) getPLSNATSubnets() ([]v1alpha1.Subnet, error) {
	if len(t.VPC.Status.PLSNATSubnets) == 0 {
		return nil, fmt.Errorf("pls nat subnet is not ready")
	}
	for _, subnet := range t.VPC.Status.PLSNATSubnets {
		if subnet.SubnetID == "" {
			return nil, fmt.Errorf("pls nat subnet id is empty")
		}
		if subnet.Name == "" {
			return nil, fmt.Errorf("pls nat subnet name is empty")
		}
	}
	return t.VPC.Status.PLSNATSubnets, nil
}

func buildNATIPConfigurations(subnets []v1alpha1.Subnet) []azurenetworkv1beta1.NATIPConfigurationParameters {
	configs := make([]azurenetworkv1beta1.NATIPConfigurationParameters, 0, len(subnets))
	for i, subnet := range subnets {
		configs = append(configs, azurenetworkv1beta1.NATIPConfigurationParameters{
			Name:     ptr.To(subnet.Name),
			Primary:  ptr.To(i == 0),
			SubnetID: ptr.To(subnet.SubnetID),
		})
	}
	return configs
}
