package privatelinkservicemigration

import (
	"context"
	"fmt"
	"time"

	"github.com/go-logr/logr"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/predicate"

	"github.com/tidbcloud/infra-provider/pkg/cloud/aws"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/clustercli"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkservicemigration/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkservicemigration/task/common"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/privatelinkservicemigration/task/eks"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/approute"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/privatelinkservice"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/privatelinkservicemigration"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/vpc"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
)

const (
	defaultBaseDelay = 5 * time.Second
	defaultMaxDelay  = 2 * time.Minute
)

type PrivateLinkServiceMigrationReconciler struct {
	client.Client
	Log            logr.Logger
	Scheme         *runtime.Scheme
	EventRecorder  record.EventRecorder
	plsMigrationRW privatelinkservicemigration.PrivateLinkServiceMigrationReaderWriter
	plsRW          privatelinkservice.PrivateLinkServiceReaderWriter
	vpcRW          vpc.VPCReaderWriter
	appRouteR      approute.APPRouteReader
	awsCli         aws.Client
	clusterCli     clustercli.ClusterClient
}

func NewPrivateLinkServiceMigrationReconciler(
	client client.Client,
	log logr.Logger,
	scheme *runtime.Scheme,
	awsCli aws.Client,
	clusterCli clustercli.ClusterClient,
	eventRecorder record.EventRecorder,
) *PrivateLinkServiceMigrationReconciler {
	return &PrivateLinkServiceMigrationReconciler{
		Client:         client,
		Log:            log,
		Scheme:         scheme,
		EventRecorder:  eventRecorder,
		plsMigrationRW: privatelinkservicemigration.NewPrivateLinkServiceMigrationReaderWriter(client),
		plsRW:          privatelinkservice.NewPrivateLinkServiceReaderWriter(client),
		vpcRW:          vpc.NewVPCReaderWriter(client),
		appRouteR:      approute.NewAPPRouteReader(client),
		awsCli:         awsCli,
		clusterCli:     clusterCli,
	}
}

//+kubebuilder:rbac:groups=dedicated.regionalserver.tidbcloud.com,resources=privatelinkservicemigration,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=dedicated.regionalserver.tidbcloud.com,resources=privatelinkservicemigration/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=dedicated.regionalserver.tidbcloud.com,resources=privatelinkservicemigration/finalizers,verbs=update
//+kubebuilder:rbac:groups=dedicated.regionalserver.tidbcloud.com,resources=privatelinkservice,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=dedicated.regionalserver.tidbcloud.com,resources=privatelinkservice/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=dedicated.regionalserver.tidbcloud.com,resources=privatelinkservice/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the private link service migration closer to the desired state.
func (r *PrivateLinkServiceMigrationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := r.Log.WithValues("privatelinkservicemigration", req.NamespacedName)
	log.Info("Reconciling RegionalServer PrivateLinkServiceMigration")
	start := time.Now()
	defer func() {
		log.Info("Finished reconciling RegionalServer PrivateLinkServiceMigration", "took", time.Since(start))
	}()

	var privatelinkservicemigration v1alpha1.PrivateLinkServiceMigration
	if err := r.Get(ctx, req.NamespacedName, &privatelinkservicemigration); err != nil {
		if apierrors.IsNotFound(err) {
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, fmt.Errorf("failed to get PrivateLinkServiceMigration %s: %w", req.NamespacedName, err)
	}

	if utils.IsPaused(privatelinkservicemigration.GetObjectMeta()) {
		log.Info("reconciliation is paused either through the pause annotation",
			"annotation", v1alpha1.AnnotationKeyReconciliationPaused,
			"privatelinkservicemigration", req.NamespacedName.String(),
		)
		r.EventRecorder.Event(&privatelinkservicemigration, "ReconciliationPaused", "reconciliation is paused through the pause annotation", v1alpha1.AnnotationKeyReconciliationPaused)
		return ctrl.Result{}, nil
	}

	switch privatelinkservicemigration.Status.Phase {
	case v1alpha1.PrivateLinkServiceMigrationPhaseNone:
		return r.handleNone(ctx, &privatelinkservicemigration)
	case v1alpha1.PrivateLinkServiceMigrationPhasePending:
		return r.handlePending(ctx, &privatelinkservicemigration)
	case v1alpha1.PrivateLinkServiceMigrationPhaseRunning:
		return r.handleRunning(ctx, &privatelinkservicemigration)
	case v1alpha1.PrivateLinkServiceMigrationPhaseCompleted:
		return r.handleCompleted(ctx, &privatelinkservicemigration)
	case v1alpha1.PrivateLinkServiceMigrationPhaseFailed:
		return r.handleFailed(ctx, &privatelinkservicemigration)
	default:
		log.Info("unknown phase, ignore it")
		return ctrl.Result{}, nil
	}
}

func (r *PrivateLinkServiceMigrationReconciler) handleNone(ctx context.Context, privatelinkservicemigration *v1alpha1.PrivateLinkServiceMigration) (ctrl.Result, error) {
	log := r.Log.WithValues("privatelinkservicemigration", privatelinkservicemigration.Name)
	log.Info("Handling none PrivateLinkServiceMigration")
	privatelinkservicemigration.Status.Phase = v1alpha1.PrivateLinkServiceMigrationPhasePending
	privatelinkservicemigration.Status.Reason = "Begin to migrate, set to pending"
	r.EventRecorder.Event(privatelinkservicemigration, "Normal", "Begin to migrate, set to pending", "Begin to migrate")
	return ctrl.Result{}, r.plsMigrationRW.UpdateStatus(ctx, privatelinkservicemigration)
}

func (r *PrivateLinkServiceMigrationReconciler) handlePending(ctx context.Context, privatelinkservicemigration *v1alpha1.PrivateLinkServiceMigration) (ctrl.Result, error) {
	log := r.Log.WithValues("privatelinkservicemigration", privatelinkservicemigration.Name)
	log.Info("Handling pending PrivateLinkServiceMigration")
	privatelinkservicemigration.Status.Phase = v1alpha1.PrivateLinkServiceMigrationPhaseRunning
	privatelinkservicemigration.Status.Reason = "Begin to migrate, set to running"
	r.EventRecorder.Event(privatelinkservicemigration, "Normal", "Begin to migrate, set to running", "Begin to migrate")
	return ctrl.Result{}, r.plsMigrationRW.UpdateStatus(ctx, privatelinkservicemigration)
}

func (r *PrivateLinkServiceMigrationReconciler) handleRunning(ctx context.Context, privatelinkservicemigration *v1alpha1.PrivateLinkServiceMigration) (ctrl.Result, error) {
	log := r.Log.WithValues("privatelinkservicemigration", privatelinkservicemigration.Name)
	log.Info("Handling running PrivateLinkServiceMigration")
	pls := &v1alpha1.PrivateLinkService{}
	if err := r.plsRW.Get(ctx, client.ObjectKey{
		Namespace: privatelinkservicemigration.Spec.PrivateLinkServiceNamespace,
		Name:      privatelinkservicemigration.Spec.PrivateLinkServiceName,
	}, pls); err != nil {
		return ctrl.Result{}, fmt.Errorf("failed to get private link service: %w", err)
	}
	// only support aws now
	if pls.Spec.Provider != consts.ProviderTypeEKS {
		return ctrl.Result{}, fmt.Errorf("only support eks private link service migration now")
	}
	vpc := &v1alpha1.VPC{}
	if err := r.vpcRW.Get(ctx, client.ObjectKey{
		Namespace: pls.Namespace,
		Name:      pls.Spec.VPCRef.Name,
	}, vpc); err != nil {
		return ctrl.Result{}, fmt.Errorf("failed to get vpc: %w", err)
	}
	c, err := (&task.Context{
		Local:                         r.Client,
		Logger:                        log,
		VPC:                           vpc,
		PrivateLinkService:            pls,
		PrivateLinkServiceRW:          r.plsRW,
		PrivateLinkServiceMigration:   privatelinkservicemigration,
		PrivateLinkServiceMigrationRW: r.plsMigrationRW,
		APPRouteR:                     r.appRouteR,
		ClusterCli:                    r.clusterCli,
	}).Default()
	if err != nil {
		return ctrl.Result{}, fmt.Errorf("failed to create task context: %w", err)
	}

	tasks := []deploy.Task{
		common.NewPausePls(c, r.awsCli),      // Pause the private link service
		eks.NewRejectEndpoints(c, r.awsCli),  // Reject the private link service endpoints
		eks.NewCutoverPls(c, r.awsCli),       // Cutover the private link service load balancer
		eks.NewApproveEndpoints(c, r.awsCli), // Approve the private link service endpoints
		common.NewResumePls(c, r.awsCli),     // Resume the private link service and update the private link service tidbgroup
	}

	d := deploy.NewDeployer(tasks, nil, log)
	results := d.Deploy(ctx)
	res, err, retriableErrs := deploy.MinRequeue(results)
	if err == nil && retriableErrs == nil {
		privatelinkservicemigration.Status.Phase = v1alpha1.PrivateLinkServiceMigrationPhaseCompleted
		privatelinkservicemigration.Status.Reason = "Migrate completed"
		r.EventRecorder.Event(privatelinkservicemigration, "Normal", "Migrate completed", "Migrate completed")
	}
	if err != nil {
		log.Error(err, "Failed to migrate private link service migration, requeue")
	}

	return res, r.plsMigrationRW.UpdateStatus(ctx, privatelinkservicemigration)
}

func (r *PrivateLinkServiceMigrationReconciler) handleCompleted(_ context.Context, privatelinkservicemigration *v1alpha1.PrivateLinkServiceMigration) (ctrl.Result, error) {
	log := r.Log.WithValues("privatelinkservicemigration", privatelinkservicemigration.Name)
	log.Info("Handling completed PrivateLinkServiceMigration, no need to do anything")
	return ctrl.Result{}, nil
}

func (r *PrivateLinkServiceMigrationReconciler) handleFailed(_ context.Context, privatelinkservicemigration *v1alpha1.PrivateLinkServiceMigration) (ctrl.Result, error) {
	log := r.Log.WithValues("privatelinkservicemigration", privatelinkservicemigration.Name)
	log.Info("Handling failed PrivateLinkServiceMigration")
	privatelinkservicemigration.Status.Phase = v1alpha1.PrivateLinkServiceMigrationPhaseFailed
	privatelinkservicemigration.Status.Reason = "Migrate failed"
	r.EventRecorder.Event(privatelinkservicemigration, "Error", "Migrate failed", "Migrate failed")
	return ctrl.Result{}, nil
}

func (r *PrivateLinkServiceMigrationReconciler) SetupWithManager(mgr ctrl.Manager, pds ...predicate.Predicate) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&v1alpha1.PrivateLinkServiceMigration{}, builder.WithPredicates(pds...)).
		WithOptions(controller.Options{
			MaxConcurrentReconciles: 1,
			RateLimiter:             workqueue.NewTypedItemExponentialFailureRateLimiter[ctrl.Request](defaultBaseDelay, defaultMaxDelay),
		}).
		Complete(r)
}
