package gc

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/appbox"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/internet"
	"k8s.io/apimachinery/pkg/fields"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type internetGCWorker struct {
	conf       config.GCConfig
	log        logr.Logger
	internetrw internet.InternetReaderWriter
	appboxr    appbox.APPBoxReader
}

func NewInternetGCWorker(cli client.Client, log logr.Logger, conf config.GCConfig) *internetGCWorker {
	return &internetGCWorker{
		conf:       conf,
		log:        log,
		internetrw: internet.NewInternetReaderWriter(cli),
		appboxr:    appbox.NewAPPBoxReader(cli),
	}
}

func (g *internetGCWorker) scanIdle(ctx context.Context) error {
	internets := &v1alpha1.InternetList{}
	if err := g.internetrw.List(ctx, internets); err != nil {
		return err
	}
	for _, internet := range internets.Items {
		internet := internet
		if internet.Status.Phase != v1alpha1.InternetPhaseActive {
			continue
		}
		if internet.GetAnnotations()[v1alpha1.SkipGCAnnotationKey] == v1alpha1.SkipGCAnnotationVal {
			g.log.Info("skip gc", "internet", internet.Name)
			continue
		}
		if err := g.checkIdleInternetOne(ctx, &internet); err != nil {
			g.log.Error(err, "check idle internet failed", "internet", internet.Name)
			continue
		}
	}
	return nil
}

// checkIdleInternetOne checks if the internet is idle at this time.
// if the internet is idle, update the idleStartTime annotation.
// if the internet is idle for a long time, set phase to ReadyForDelete.
// if the internet is not idle, remove the idleStartTime annotation.
func (g *internetGCWorker) checkIdleInternetOne(ctx context.Context, internet *v1alpha1.Internet) error {
	if !g.isIdleInternet(ctx, internet) {
		if internet.GetAnnotations()[v1alpha1.AnnotationKeyIdelStartTime] != "" {
			delete(internet.Annotations, v1alpha1.AnnotationKeyIdelStartTime)
			return g.internetrw.Update(ctx, internet)
		}
		return nil
	}

	idleStartTimeStr := internet.GetAnnotations()[v1alpha1.AnnotationKeyIdelStartTime]
	if idleStartTimeStr == "" {
		if internet.Annotations == nil {
			internet.Annotations = make(map[string]string)
		}
		internet.Annotations[v1alpha1.AnnotationKeyIdelStartTime] = time.Now().Format(time.RFC3339)
		return g.internetrw.Update(ctx, internet)
	}

	idleStartTime, err := time.Parse(time.RFC3339, idleStartTimeStr)
	if err != nil {
		g.log.Error(err, "parse idleStartTime failed", "internet", internet.Name, "idleStartTime", idleStartTime)
		return err
	}
	idleTime := time.Since(idleStartTime).Seconds()
	if idleTime > g.conf.IdleTimeThreshold.Duration.Seconds() {
		g.log.Info("internet is idle for a long time, set phase to ReadyForDelete",
			"internet", internet.Name, "idleTime", idleTime, "threshold", g.conf.IdleTimeThreshold.Duration.Seconds())
		internet.Status.Phase = v1alpha1.InternetPhaseReadyForDelete
		if err := g.internetrw.UpdateStatus(ctx, internet); err != nil {
			g.log.Error(err, "update internet status failed", "internet", internet.Name)
			return err
		}
		internet.Annotations[v1alpha1.AnnotationKeyReadyForDeleteStartTime] = time.Now().Format(time.RFC3339)
		if err := g.internetrw.Update(ctx, internet); err != nil {
			g.log.Error(err, "update internet failed", "internet", internet.Name)
			return err
		}
	}
	return nil
}

func (g *internetGCWorker) isIdleInternet(ctx context.Context, internet *v1alpha1.Internet) bool {
	appboxs := &v1alpha1.APPBoxList{}
	opts := &client.ListOptions{
		FieldSelector: fields.OneTermEqualSelector(controllers.VPCNameField, internet.Spec.VPCRef.Name),
		Namespace:     internet.Namespace,
	}
	if err := g.appboxr.List(ctx, appboxs, opts); err != nil {
		g.log.Error(err, "list appbox failed", "internet", internet.Name)
		return false
	}
	return len(appboxs.Items) == 0
}

func (g *internetGCWorker) cleanIdle(ctx context.Context) error {
	internets := &v1alpha1.InternetList{}
	if err := g.internetrw.List(ctx, internets); err != nil {
		return err
	}
	for _, internet := range internets.Items {
		internet := internet
		if internet.Status.Phase != v1alpha1.InternetPhaseReadyForDelete {
			continue
		}
		if internet.GetAnnotations()[v1alpha1.SkipGCAnnotationKey] == v1alpha1.SkipGCAnnotationVal {
			continue
		}
		if err := g.cleanIdleInternetOne(ctx, &internet); err != nil {
			g.log.Error(err, "clean idle internet failed", "internet", internet.Name)
			continue
		}
	}
	return nil
}

func (g *internetGCWorker) cleanIdleInternetOne(ctx context.Context, internet *v1alpha1.Internet) error {
	if !g.isIdleInternet(ctx, internet) {
		g.log.Info("internet is not idle, set phase to Active", "internet", internet.Name)
		internet.Status.Phase = v1alpha1.InternetPhaseActive
		if err := g.internetrw.UpdateStatus(ctx, internet); err != nil {
			g.log.Error(err, "update internet status failed", "internet", internet.Name)
			return err
		}
		if internet.GetAnnotations()[v1alpha1.AnnotationKeyReadyForDeleteStartTime] != "" {
			delete(internet.Annotations, v1alpha1.AnnotationKeyReadyForDeleteStartTime)
			if err := g.internetrw.Update(ctx, internet); err != nil {
				g.log.Error(err, "update internet failed", "internet", internet.Name)
				return err
			}
		}
		return nil
	}

	readyDeleteStartTimeStr := internet.GetAnnotations()[v1alpha1.AnnotationKeyReadyForDeleteStartTime]
	if readyDeleteStartTimeStr == "" {
		if internet.Annotations == nil {
			internet.Annotations = make(map[string]string)
		}
		internet.Annotations[v1alpha1.AnnotationKeyReadyForDeleteStartTime] = time.Now().Format(time.RFC3339)
		if err := g.internetrw.Update(ctx, internet); err != nil {
			g.log.Error(err, "update internet failed", "internet", internet.Name)
			return err
		}
	}
	readyDeleteStartTime, err := time.Parse(time.RFC3339, readyDeleteStartTimeStr)
	if err != nil {
		g.log.Error(err, "parse readyDeleteStartTime failed", "internet", internet.Name, "readyDeleteStartTime", readyDeleteStartTimeStr)
		return err
	}
	readyDeleteTime := time.Since(readyDeleteStartTime)
	if readyDeleteTime > g.conf.IdleTimeRetention.Duration {
		g.log.Info("internet is ready for delete for a long time, delete it", "internet", internet.Name, "readyDeleteTime", readyDeleteTime)
		if err := g.internetrw.Delete(ctx, internet); err != nil {
			g.log.Error(err, "delete internet failed", "internet", internet.Name)
			return err
		}
	}
	return nil
}
