// Copy from cluster-controller/controllers/networkpeer/task/common/guardian.go
package common

import (
	"context"
	"errors"
	"fmt"
	"time"

	apierrors "k8s.io/apimachinery/pkg/api/errors"

	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/samber/lo"
	"github.com/tidbcloud/infra-provider/pkg/applier"
	_ "github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	_ "github.com/tidbcloud/infra-provider/regional-server/pkg/common/constants"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpcpeering/task"
)

type Guardian struct {
	*task.BaseTask
}

func NewGuardian(ctx *task.Context) *Guardian {
	return &Guardian{BaseTask: &task.BaseTask{Context: ctx}}
}

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

// Guardian Create() ensure the Finalizer is correctly set
func (t *Guardian) Create(ctx context.Context) deploy.Result {
	logger := t.Logger
	// Add finalizer
	if !controllerutil.ContainsFinalizer(t.VPCPeering, v1alpha1.FinalizerVPCPeering) {
		controllerutil.AddFinalizer(t.VPCPeering, v1alpha1.FinalizerVPCPeering)
		// Require immediate requeue without running other tasks
		return deploy.Panic(nil).WithRequeuAfter(0)
	}
	// if we cannot get vpc, abort the create phase and requeue the task
	if t.VPCPeering.Spec.SourceVPCRef == nil {
		return deploy.Panic(errors.New("spec.sourceVPCRef is nil")).WithRequeuAfter(time.Minute)
	}
	{
		vpc := applier.NewObject[*v1alpha1.VPC](
			t.VPCPeering.Spec.SourceVPCRef.Namespace,
			t.VPCPeering.Spec.SourceVPCRef.Name,
		)
		key := client.ObjectKeyFromObject(vpc)
		if err := t.Local.Get(ctx, key, vpc); err != nil {
			if apierrors.IsNotFound(err) {
				logger.Info("source vpc cr not found", "vpc", key.String())
			} else {
				logger.Error(err, "get source vpc cr failed", "vpc", key.String())
			}
			return deploy.Panic(err).WithRequeuAfter(time.Minute)
		}
		t.VPC = vpc
	}
	// check vpcpeering.spec.AccountRef is not empty
	{
		if t.VPCPeering.Spec.AccountRef == nil {
			return deploy.Panic(errors.New("spec.accountRef is nil")).WithRequeuAfter(time.Minute)
		}
		if t.VPCPeering.Spec.AccountRef.ProviderConfigName == "" {
			return deploy.Panic(errors.New("spec.accountRef.providerConfigName is nil")).WithRequeuAfter(time.Minute)
		}
	}

	// check vpc phase before doing vpcpeering, may not correct.
	switch phase := t.VPC.Status.Phase; phase {
	case v1alpha1.VPCPhaseActive:
		// do nothing
	case "", v1alpha1.VPCPhaseCreating, v1alpha1.VPCPhasePending, v1alpha1.VPCPhaseReadyForCreate, v1alpha1.VPCPhaseFailed:
		// can requeue
		return deploy.Error(fmt.Errorf("vpc is not active during peering, current status is %s", phase)).WithRequeuAfter(time.Minute)
	case v1alpha1.VPCPhaseDeleted, v1alpha1.VPCPhaseDeleting, v1alpha1.VPCPhaseReadyForDelete:
		// abort without requeue
		return deploy.Panic(fmt.Errorf("cannot do peering when vpc phase is %s", phase))
	default:
		// abort without requeue
		return deploy.Panic(fmt.Errorf("unexpected vpc phase during peering: %s", phase))
	}
	// abort with requeue
	if t.VPC.Status.VPC.VPCID == "" {
		return deploy.Panic(errors.New("vpc.status.vpcId is empty")).WithRequeuAfter(time.Minute)
	}

	internets := &v1alpha1.InternetList{}
	if err := t.Local.List(ctx, internets, client.MatchingLabels{
		v1alpha1.VPCNameKey: t.VPC.Name,
	}); err != nil {
		return deploy.Panic(err).WithRequeuAfter(time.Minute)
	}
	if len(internets.Items) == 0 {
		return deploy.Panic(errors.New("no internet found")).WithRequeuAfter(5 * time.Minute)
	}
	internet := lo.Filter(internets.Items, func(item v1alpha1.Internet, _ int) bool {
		return item.Status.Phase == v1alpha1.InternetPhaseActive
	})
	if len(internet) == 0 {
		return deploy.Panic(errors.New("no active internet found")).WithRequeuAfter(time.Minute)
	}
	t.Internet = &internet[0]

	if t.VPCPeering.GetAnnotations()[v1alpha1.AnnotationKeySourceVPCID] == "" {
		t.VPCPeering.GetAnnotations()[v1alpha1.AnnotationKeySourceVPCID] = t.VPC.Status.VPC.VPCID
	}
	if t.VPCPeering.GetAnnotations()[v1alpha1.AnnotationKeySourceVPCCIDR] == "" {
		t.VPCPeering.GetAnnotations()[v1alpha1.AnnotationKeySourceVPCCIDR] = t.VPC.Spec.CIDR
	}
	if t.VPCPeering.GetAnnotations()[v1alpha1.AnnotationKeySourceAccountID] == "" {
		t.VPCPeering.GetAnnotations()[v1alpha1.AnnotationKeySourceAccountID] = t.VPC.Spec.AccountRef.AccountID
	}
	return deploy.Done()
}

// Guardian Delete() ensure nothing.
func (t *Guardian) Delete(ctx context.Context) deploy.Result {
	return deploy.Done()
}
