package eks

import (
	"context"
	"fmt"

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

	"github.com/tidbcloud/eks-provider/controllers/network/task"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

const (
	Finalizer = "network.cloud.pingcap.com/finalizer"
)

// 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"
}

// Create returns an error or makes the network phase be one of
// - NetworkPhaseCreating
// - NetworkPhaseNormal
func (t *Guardian) Create(ctx context.Context) deploy.Result {
	// ignore resource that doesn't belong to this region
	if !util.ContainsRegionLabel(t.Network) {
		if ok, err := util.CheckNetworkRegion(t.Config.Network.Region, t.Network); err != nil {
			return deploy.Panic(err)
		} else if !ok {
			return deploy.Panic(nil)
		}
		// ensure the object contains the region label
		if !util.EnsureRegion(t.Network, t.Network.Spec.Region) {
			return deploy.Panic(nil).WithRequeuAfter(0)
		}
	}
	if !controllerutil.ContainsFinalizer(t.Network, Finalizer) {
		controllerutil.AddFinalizer(t.Network, Finalizer)
		// Require immediate requeue without running other tasks
		return deploy.Panic(nil).WithRequeuAfter(0)
	}

	switch phase := t.Network.Status.Phase; phase {
	case "":
		t.Network.Status.Phase = cloudv1.NetworkPhaseCreating
	case cloudv1.NetworkPhaseCreating, cloudv1.NetworkPhaseHibernated, cloudv1.NetworkPhaseWakeUp, cloudv1.NetworkPhaseNormal:
	default:
		return deploy.Panic(fmt.Errorf("unexpected network phase during creation: %s", phase))
	}
	return deploy.Done()
}

func (t *Guardian) Delete(ctx context.Context) deploy.Result {
	switch phase := t.Network.Status.Phase; phase {
	case cloudv1.NetworkPhaseCreating, cloudv1.NetworkPhaseNormal, "":
		t.Network.Status.Phase = cloudv1.NetworkPhasePendingDelete
		// Require immediate requeue without running other tasks
		return deploy.Panic(nil).WithRequeuAfter(0)
	case cloudv1.NetworkPhasePendingDelete:
		err := t.readyToDelete(ctx)
		if err != nil {
			return deploy.Panic(fmt.Errorf("network is not ready to delete: %v", err))
		}
		t.Network.Status.Phase = cloudv1.NetworkPhaseDeleting
	case cloudv1.NetworkPhaseDeleting:
	default:
		return deploy.Panic(fmt.Errorf("unexpected network phase during deletion: %s", phase))
	}
	return deploy.Done()
}

func (t *Guardian) readyToDelete(ctx context.Context) error {
	clusterList := &cloudv1.ClusterList{}
	optCluster := k8s.ListOpts[*cloudv1.Cluster]{
		ObjectList: clusterList,
		Unpack:     func() []*cloudv1.Cluster { return k8s.MapPtr(clusterList.Items) },
	}
	clusters, err := optCluster.ListFilterBy(ctx, t.Local, func(cluster *cloudv1.Cluster) bool {
		return cluster.Spec.NetworkName == t.Network.Name
	}, client.InNamespace(t.Network.Namespace))
	if client.IgnoreNotFound(err) != nil {
		return fmt.Errorf("list clusters error: %v", err)
	}
	if len(clusters) != 0 {
		return fmt.Errorf("clusters to be deleted: %d", len(clusters))
	}
	return nil
}
