package network

import (
	"context"
	"time"

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

	"github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/eks-provider/controllers/network/task"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/metrics"
	"github.com/tidbcloud/infra-provider/pkg/o11y/tracing"
	"github.com/tidbcloud/infra-provider/pkg/util"
)

func updateCreate(ctx context.Context, t *task.Context, results []deploy.Result) (res ctrl.Result, err error) {
	var retriableErr error
	res, err, retriableErr = deploy.MinRequeue(results)
	if err == nil && retriableErr == nil {
		// Hibernated status will update to WakingUp status when create a cluster in the network
		// Hibernated network status can not update to normal
		if t.Network.Status.Phase != cloudv1.NetworkPhaseNormal && t.Network.Status.Phase != cloudv1.NetworkPhaseHibernated {
			age := time.Since(t.Network.CreationTimestamp.Time)

			key := "first-pass-when-creating.cloud.pingcap.com/All"
			if _, ok := t.Network.Annotations[key]; !ok {
				if t.Network.Annotations == nil {
					t.Network.Annotations = make(map[string]string)
				}
				t.Network.Annotations[key] = age.String()
			}

			log.FromContext(ctx).Info("mark network become running when creating network", "sinceCreate", age)
			metrics.NetworkFirstPassStageSeconds.WithLabelValues("All").Observe(age.Seconds())
			t.Network.Status.Phase = cloudv1.NetworkPhaseNormal
		}
	}

	if retriableErr != nil {
		log.FromContext(ctx).Info("retriable error", "error", retriableErr)
	}

	// ref https://github.com/tidbcloud/eks-provider/pull/481
	// remove this once there is no legacy 'NotReady' phase networks
	if t.Network.Status.Phase == "NotReady" {
		t.Network.Status.Phase = cloudv1.NetworkPhaseNormal
	}

	switch t.Network.Spec.Provider {
	case consts.ProviderTypeEKS:
		// refactor to avoid updating EKSNetworkSpec at tasks
		// TODO: update EKSNetworkSpec at here
		// TODO: update EKSNetworkStatus at here
	case consts.ProviderTypeGKE:
		if err := v1alpha1.SetGKENetworkSpec(t.Network, t.GKENetworkSpec); err != nil {
			return res, err
		}
		// TODO: update GKENetworkStatus at here
	}

	if e := update(ctx, t.Local, t.Network); e != nil {
		return res, e
	}
	return res, err
}

func updateDelete(ctx context.Context, t *task.Context, results []deploy.Result) (res ctrl.Result, err error) {
	var deleted bool
	// If the network is pending delete, then the deletion tasks are not actually performed
	if t.Network.Status.Phase != cloudv1.NetworkPhasePendingDelete {
		var retriableErr error
		res, err, retriableErr = deploy.MinRequeue(results)
		if err == nil && retriableErr == nil {
			deleted = true
		}
		if t.Network.Status.Phase != cloudv1.NetworkPhaseHibernating {
			t.Network.Status.Phase = cloudv1.NetworkPhaseDeleting
		}

		if retriableErr != nil {
			log.FromContext(ctx).Info("retriable error", "error", retriableErr)
		}
	}

	if deleted {
		// If deleted is true, update hiberneting status to hibernated
		if t.Network.Status.Phase == cloudv1.NetworkPhaseHibernating {
			t.Network.Status.Phase = cloudv1.NetworkPhaseHibernated
		} else {
			controllerutil.RemoveFinalizer(t.Network, task.Finalizer)
		}
	}
	if e := update(ctx, t.Local, t.Network); e != nil {
		return res, e
	}
	return res, err
}

func update(ctx context.Context, cli client.Client, Network *cloudv1.Network) error {
	tracing.InjectTraceContext(ctx, Network)

	util.PopulateRawExtension(&Network.Status.FromProvider)
	networkStatus := Network.Status.DeepCopy()
	if err := cli.Update(ctx, Network); err != nil {
		return err
	}
	Network.Status = *networkStatus
	return cli.Status().Update(ctx, Network)
}
