package task

import (
	"context"
	"errors"
	"fmt"
	"maps"
	"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/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type VPC struct {
	*BaseTask
}

func NewVPC(ctx *Context) *VPC {
	return &VPC{BaseTask: &BaseTask{Context: ctx}}
}

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

func (t *VPC) Create(ctx context.Context) deploy.Result {
	t.BeginAt = util.Pointer(time.Now())
	if t.DataPlane.Status.Vpc != nil && t.DataPlane.Status.Vpc.Name != "" {
		return t.syncLabels(ctx, t.DataPlane.Status.Vpc)
	}

	privateLinkEndpointZones, err := t.resolvePrivateLinkEndpointZones(ctx)
	if err != nil {
		return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
	}
	if t.Config.Provider == consts.ProviderTypeACK && len(privateLinkEndpointZones) > 0 {
		if _, err := util.GetAliyunPrivateLinkEndpointSubnetCIDRs(
			t.DataPlane.Spec.VpcCidr,
			privateLinkEndpointZones,
		); err != nil {
			return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
		}
	}

	vpc := &v1alpha1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Name:      t.GetSubResourceName(),
			Namespace: t.GetSubResourceNamespace(),
			// add labels project，tennetid，region
			Labels: t.desiredLabels(),
		},
		Spec: v1alpha1.VPCSpec{
			Provider:                 t.Config.Provider,
			Region:                   t.Config.RegionConfig.Name,
			Zones:                    t.DataPlane.Spec.AvailabilityZones,
			NodeZones:                t.DataPlane.Spec.NodeZones,
			PrivateLinkEndpointZones: privateLinkEndpointZones,
			CIDR:                     t.DataPlane.Spec.VpcCidr,
			AccountRef:               &t.DataPlane.Spec.AccountRef,
		},
	}
	if err := t.VpcRW.Create(ctx, vpc); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
		}
		t.Logger.Info("vpc already exists", zap.String("vpc_name", vpc.Name))
	}

	t.DataPlane.Status.Vpc = &v1alpha1.Reference{
		Namespace: vpc.Namespace,
		Name:      vpc.Name,
	}
	return deploy.Done()
}

func (t *VPC) desiredLabels() map[string]string {
	return util.MergeMaps(t.Config.ResourceLabels, t.DataPlane.Spec.CloudTags, map[string]string{
		v1alpha1.RegionKey:      t.Config.RegionConfig.Name,
		v1alpha1.AccountIDKey:   t.DataPlane.Spec.AccountRef.AccountID,
		v1alpha1.DataPlaneIDKey: t.DataPlane.Name,
	})
}

func mergeDesiredLabels(existing, desired map[string]string) map[string]string {
	return util.MergeMaps(existing, desired)
}

func (t *VPC) syncLabels(ctx context.Context, ref *v1alpha1.Reference) deploy.Result {
	vpc := &v1alpha1.VPC{}
	if err := t.VpcRW.Get(ctx, client.ObjectKey{Namespace: ref.Namespace, Name: ref.Name}, vpc); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	labels := mergeDesiredLabels(vpc.Labels, t.desiredLabels())
	if maps.Equal(vpc.Labels, labels) {
		return deploy.Done()
	}
	vpc.Labels = labels
	if err := t.VpcRW.Update(ctx, vpc); err != nil {
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Done()
}

func (t *VPC) resolvePrivateLinkEndpointZones(ctx context.Context) ([]string, error) {
	if !t.Config.ShouldProvisionPrivateLinkEndpointSubnets(t.DataPlane.Spec.NodeZones) {
		return nil, nil
	}
	if t.PrivateLinkEndpointZoneResolver == nil {
		return nil, errors.New("private link endpoint zone resolver is not configured")
	}
	accountID := t.DataPlane.Spec.AccountRef.AccountID
	if accountID == "" {
		return nil, errors.New("account id is required to resolve private link endpoint zones")
	}
	roleName := t.DataPlane.Spec.AccountRef.Role
	if roleName == "" {
		roleName = t.Config.DataPlaneRoleName
	}
	if roleName == "" {
		return nil, errors.New("dataplane role name is required to resolve private link endpoint zones")
	}
	switch t.Config.Provider {
	case consts.ProviderTypeEKS:
		return t.PrivateLinkEndpointZoneResolver.Resolve(
			ctx,
			util.GetRoleARN(accountID, roleName),
			t.Config.AWSDataPlaneExternalID,
			t.Config.RegionConfig.Name,
		)
	case consts.ProviderTypeACK:
		return t.PrivateLinkEndpointZoneResolver.Resolve(
			ctx,
			util.GetRAMRoleARN(accountID, roleName),
			"",
			t.Config.RegionConfig.Name,
		)
	default:
		return nil, nil
	}
}

func (t *VPC) PostCreate(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.Vpc == nil {
		return deploy.Panic(errors.New("vpc is nil in status"))
	}

	key := client.ObjectKey{
		Namespace: t.DataPlane.Status.Vpc.Namespace,
		Name:      t.DataPlane.Status.Vpc.Name,
	}
	vpc := new(v1alpha1.VPC)
	if err := t.VpcRW.Get(ctx, key, vpc); err != nil {
		return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
	}

	if vpc.Status.Phase == v1alpha1.VPCPhaseFailed {
		return deploy.Panic(fmt.Errorf("vpc %s is failed", vpc.Name))
	}
	if vpc.Status.Phase != v1alpha1.VPCPhaseActive {
		return deploy.Panic(fmt.Errorf("vpc %s is not active, phase is %s", vpc.Name, vpc.Status.Phase)).
			WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Done()
}

func (t *VPC) Delete(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.Vpc == nil {
		return deploy.Done()
	}

	vpc := &v1alpha1.VPC{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: t.DataPlane.Status.Vpc.Namespace,
			Name:      t.DataPlane.Status.Vpc.Name,
		},
	}
	if err := t.VpcRW.Delete(ctx, vpc); err != nil {
		if !apierrors.IsNotFound(err) {
			return deploy.Panic(err).WithRequeuAfter(requeueAfterDuration)
		}
	}
	return deploy.Done()
}

func (t *VPC) PostDelete(ctx context.Context) deploy.Result {
	if t.DataPlane.Status.Vpc == nil {
		return deploy.Done()
	}

	key := client.ObjectKey{
		Namespace: t.DataPlane.Status.Vpc.Namespace,
		Name:      t.DataPlane.Status.Vpc.Name,
	}
	vpc := new(v1alpha1.VPC)
	if err := t.VpcRW.Get(ctx, key, vpc); err != nil {
		if apierrors.IsNotFound(err) {
			return deploy.Done()
		}
		return deploy.Error(err).WithRequeuAfter(requeueAfterDuration)
	}
	return deploy.Error(fmt.Errorf("vpc %s is not deleted", vpc.Name)).WithRequeuAfter(requeueAfterDuration)
}
