package common

import (
	"context"
	"fmt"
	"time"

	"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/appbox/task"
	"k8s.io/apimachinery/pkg/fields"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/infra-provider/pkg/deploy"
)

// Guardian guards the deletion and creation of the network.
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"
}

func (t *Guardian) Create(ctx context.Context) deploy.Result {
	// Ensure finalizer
	if !controllerutil.ContainsFinalizer(t.APPBox, task.Finalizer) {
		controllerutil.AddFinalizer(t.APPBox, task.Finalizer)
		// Require immediate requeue without running other tasks
		return deploy.Panic(nil).WithRequeuAfter(0)
	}

	// Check phase
	switch phase := t.APPBox.Status.Phase; phase {
	case "", v1alpha1.APPBoxPhasePending, v1alpha1.APPBoxPhaseFailed:
		if t.readyForCreate(ctx) {
			t.APPBox.Status.Phase = v1alpha1.APPBoxPhaseCreating
			t.APPBox.Status.Reason = "VPCReady"
		} else {
			t.APPBox.Status.Phase = v1alpha1.APPBoxPhasePending
			t.APPBox.Status.Reason = "VPCNotReady"
			return deploy.Panic(fmt.Errorf("appbox is not ready for creation")).WithRequeuAfter(30 * time.Second)
		}
	case v1alpha1.APPBoxPhaseCreating, v1alpha1.APPBoxPhaseActive, v1alpha1.APPBoxPhaseReadyForDelete:
		// APPBoxPhaseReadyForDelete is set by api.
	default:
		return deploy.Panic(fmt.Errorf("unexpected APPBox phase during creation: %s", phase))
	}

	return deploy.Done()
}

func (t *Guardian) Delete(ctx context.Context) deploy.Result {
	if err := t.readyToDelete(ctx); err != nil {
		t.APPBox.Status.Reason = "RelatedResourcesNotDeleted"
		return deploy.Panic(err).WithRequeuAfter(30 * time.Second)
	}
	return deploy.Done()
}

func (t *Guardian) readyForCreate(ctx context.Context) bool {
	// Check if the VPC is active
	if t.VPC.Status.Phase != v1alpha1.VPCPhaseActive {
		return false
	}
	// Check if there are other appboxes in the same vpc
	opt := &client.ListOptions{
		Namespace:     t.APPBox.Namespace,
		FieldSelector: fields.OneTermEqualSelector(controllers.VPCNameField, t.APPBox.Spec.VPCRef.Name),
	}
	appboxList := &v1alpha1.APPBoxList{}
	if err := t.APPBoxRW.List(ctx, appboxList, opt); err != nil {
		t.Logger.Error(err, "failed to list appboxes")
		return false
	}
	// There should be only one appbox in the vpc
	return len(appboxList.Items) == 1
}

func (t *Guardian) readyToDelete(ctx context.Context) error {
	relatedAppRouteList := &v1alpha1.APPRouteList{}
	listOpts := &client.ListOptions{
		FieldSelector: fields.OneTermEqualSelector(controllers.APPBoxNameField, t.APPBox.Name),
		Namespace:     t.APPBox.Namespace,
	}
	if err := t.Local.List(ctx, relatedAppRouteList, listOpts); err != nil {
		return err
	}
	if len(relatedAppRouteList.Items) != 0 {
		return fmt.Errorf("approutes to be deleted: %d", len(relatedAppRouteList.Items))
	}

	relatedO11yRouteList := &v1alpha1.O11yRouteList{}
	// only check the o11yroute for tidb cluster(containing v1alpha1.ClusterIDKey Label),
	// since we manage the k8s-level o11yroute in appbox tasks.
	if err := t.Local.List(ctx, relatedO11yRouteList, listOpts, client.HasLabels{v1alpha1.ClusterIDKey}); err != nil {
		return err
	}
	if len(relatedO11yRouteList.Items) != 0 {
		return fmt.Errorf("o11yroutes to be deleted: %d", len(relatedO11yRouteList.Items))
	}

	return nil
}
