package common

import (
	"context"
	"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/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/approute/task"
)

// Guardian adds and removes the finalizer of the approute.
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 {
	log := t.Logger
	// Add finalizer
	if !controllerutil.ContainsFinalizer(t.APPRoute, v1alpha1.Finalizer) {
		controllerutil.AddFinalizer(t.APPRoute, v1alpha1.Finalizer)
		// Require immediate requeue without running other tasks
		return deploy.Panic(nil).WithRequeuAfter(0)
	}

	defer func() {
		if err := t.APPRouteRW.UpdateStatus(ctx, t.APPRoute); err != nil {
			t.Logger.Error(err, "failed to update approute status")
		}
	}()

	appbox := &v1alpha1.APPBox{}
	appboxObjKey := client.ObjectKey{Namespace: t.APPRoute.Spec.APPBoxRef.Namespace, Name: t.APPRoute.Spec.APPBoxRef.Name}
	if err := t.Local.Get(ctx, appboxObjKey, appbox); err != nil {
		if apierrors.IsNotFound(err) {
			log.Info("APPBox resource not found", "key", appboxObjKey.String())
			return deploy.Panic(nil).WithRequeuAfter(time.Minute)
		}
		log.Error(err, "failed to get APPBox resource", "key", appboxObjKey.String())
		return deploy.Panic(err).WithRequeuAfter(time.Minute)
	}
	t.APPBox = appbox

	// Check approute phase
	switch phase := t.APPRoute.Status.Phase; phase {
	case "", v1alpha1.APPRoutePhasePending, v1alpha1.APPRoutePhaseFailed:
		isActive := t.waitAPPBoxActive()
		if !isActive {
			log.Info("APPBox is not active yet, waiting for it to be active")
			t.APPRoute.Status.Phase = v1alpha1.APPRoutePhasePending
			return deploy.Panic(nil).WithRequeuAfter(time.Minute)
		}
		t.APPRoute.Status.Phase = v1alpha1.APPRoutePhaseCreating
	case v1alpha1.APPRoutePhaseCreating, v1alpha1.APPRoutePhaseActive:
	default:
		return deploy.Panic(fmt.Errorf("unexpected APPRoute phase during creation: %s", phase))
	}

	return deploy.Done()
}

func (t *Guardian) waitAPPBoxActive() bool {
	return t.APPBox.Status.Phase == v1alpha1.APPBoxPhaseActive
}

func (t *Guardian) Delete(ctx context.Context) deploy.Result {
	// TODO: check approute phase
	return deploy.Done()
}
