package ack

import (
	"context"
	"fmt"
	"time"

	aliplv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/privatelink/v1alpha1"
	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/namer"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
)

const (
	// These annotations mark whether allowed principals are set
	// annoKeyPrivateLinkAllowedPrincipals = "private-link.aws.cloud.pingcap.com/allowed-principals"
	privateLinkAllowedPrincipalsAll   = "*"
	privateLinkPayer                  = "Endpoint"
	privateLinkServiceResourceTypeNLB = "nlb"
)

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())

	ves, err := t.createEndpointService(ctx)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	if err := t.createEndpointServiceResource(ctx, ves); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	return deploy.Done()
}

func (t *EndpointService) createEndpointService(ctx context.Context) (*aliplv1alpha1.VPCEndpointService, error) {
	name := t.EndpointServiceName()
	tags := t.EffectiveCloudTags(consts.ProviderTypeACK)

	ves := &aliplv1alpha1.VPCEndpointService{
		ObjectMeta: metav1.ObjectMeta{
			Name:   name,
			Labels: t.PrivateLinkService.Labels,
		},
		Spec: aliplv1alpha1.VPCEndpointServiceSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{
					Name: t.ProviderConfigName(),
				},
				ManagementPolicies: controllers.GetResourceManagementPolicies(),
			},
			ForProvider: aliplv1alpha1.VPCEndpointServiceParameters{
				AutoAcceptConnection: ptr.To(false),
				Payer:                ptr.To(privateLinkPayer),
				ServiceResourceType:  ptr.To(privateLinkServiceResourceTypeNLB),
				ZoneAffinityEnabled:  ptr.To(true),
				Region:               ptr.To(t.PrivateLinkService.Spec.Region),
				Tags:                 util.ToMapPointer(tags),
			},
		},
	}

	if err := k8s.Apply(ctx, t.Local, ves); err != nil {
		return nil, err
	}
	return ves, nil
}

func (t *EndpointService) createEndpointServiceResource(ctx context.Context, ves *aliplv1alpha1.VPCEndpointService) error {
	zones := t.GetPrivateSubnetZoneIDs()
	if len(zones) == 0 {
		return fmt.Errorf("vpc private subnets are not ready")
	}

	for _, zone := range zones {
		name := t.endpointServiceResourceName(zone)
		vesr := &aliplv1alpha1.VpcEndpointServiceResource{
			ObjectMeta: metav1.ObjectMeta{
				Name:   name,
				Labels: t.PrivateLinkService.Labels,
			},
			Spec: aliplv1alpha1.VpcEndpointServiceResourceSpec{
				ResourceSpec: xpv1.ResourceSpec{
					ProviderConfigReference: &xpv1.Reference{
						Name: t.ProviderConfigName(),
					},
					ManagementPolicies: controllers.GetResourceManagementPolicies(),
				},
				ForProvider: aliplv1alpha1.VpcEndpointServiceResourceParameters{
					ResourceType: ptr.To(privateLinkServiceResourceTypeNLB),
					ResourceID:   ptr.To(t.PrivateLinkService.Status.LoadBalancer.CloudResourceId),
					ServiceIDRef: &xpv1.Reference{
						Name: ves.Name,
					},
					ZoneID: ptr.To(zone),
					Region: ptr.To(t.PrivateLinkService.Spec.Region),
				},
			},
		}
		if err := k8s.Apply(ctx, t.Local, vesr); err != nil {
			return err
		}
	}
	return nil
}

func (t *EndpointService) endpointServiceResourceName(zoneID string) string {
	return namer.NewName(t.EndpointServiceName(), "", zoneID, -1)
}

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

	zones := t.GetPrivateSubnetZoneIDs()
	if len(zones) == 0 {
		return deploy.Panic(fmt.Errorf("vpc private subnets are not ready")).WithRequeuAfter(time.Second * 30)
	}
	for _, zone := range zones {
		vesr := &aliplv1alpha1.VpcEndpointServiceResource{}
		if err := t.Local.Get(ctx, types.NamespacedName{Name: t.endpointServiceResourceName(zone)}, vesr); err != nil {
			return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
		}
		if !controllers.IsResourceReady(vesr.Status.ResourceStatus) {
			t.Logger.Info("EndpointServiceResource is not ready", "zone", zone, "status", ves.Status.GetCondition(xpv1.TypeReady).Status)
			return deploy.Panic(fmt.Errorf("EndpointServiceResource in zone %s is not ready", zone)).WithRequeuAfter(time.Second * 30)
		}
	}

	if err := t.setEndpointServiceStatus(ctx, ves, zones); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Second * 30)
	}
	return deploy.Done()
}

func (t *EndpointService) setEndpointServiceStatus(ctx context.Context, ves *aliplv1alpha1.VPCEndpointService, zones []string) error {
	es := v1alpha1.EndpointServiceStatus{
		Name:              t.EndpointServiceName(),
		ServiceID:         *ves.Status.AtProvider.ID,
		ServiceName:       *ves.Status.AtProvider.VPCEndpointServiceName,
		Port:              t.GetEndpointServicePort(),
		Ports:             t.GetEndpointServicePorts(),
		AvailabilityZones: zones,
	}
	t.PrivateLinkService.Status.EndpointService = es
	return t.PrivateLinkServiceRW.UpdateStatus(ctx, t.PrivateLinkService)
}

func (t *EndpointService) Delete(ctx context.Context) deploy.Result {
	zones := t.GetPrivateSubnetZoneIDs()
	for _, zone := range zones {
		vesr := &aliplv1alpha1.VpcEndpointServiceResource{
			ObjectMeta: metav1.ObjectMeta{
				Name: t.endpointServiceResourceName(zone),
			},
		}
		if err := controllers.DeleteUpboundResource(ctx, t.Local, vesr); err != nil {
			return deploy.Error(err)
		}
	}

	ves := &aliplv1alpha1.VPCEndpointService{
		ObjectMeta: metav1.ObjectMeta{
			Name: t.EndpointServiceName(),
		},
	}
	if err := controllers.DeleteUpboundResource(ctx, t.Local, ves); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *EndpointService) PostDelete(ctx context.Context) deploy.Result {
	zones := t.GetPrivateSubnetZoneIDs()
	for _, zone := range zones {
		if err := k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.endpointServiceResourceName(zone)}, &aliplv1alpha1.VpcEndpointServiceResource{}); err != nil {
			return deploy.Error(err)
		}
	}
	return deploy.ErrorOrDone(k8s.GetDeletedObject(ctx, t.Local, types.NamespacedName{Name: t.EndpointServiceName()}, &aliplv1alpha1.VPCEndpointService{}))
}
