package vpcpeering

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/tools/record"
	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/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/cloud/aws"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/vpcpeering/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/deploy"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
)

type Reconciler struct {
	kubeClient    client.Client
	awsClient     aws.Client
	logger        logr.Logger
	eventRecorder record.EventRecorder
	config        config.RootConfig

	vpcpeeringRW repository.VPCPeeringReaderWriter
	dataPlaneRW  repository.DataPlaneReaderWriter
	vpcR         repository.VPCReader
	internetR    repository.InternetReader
}

func NewReconciler(client client.Client, logger logr.Logger, recorder record.EventRecorder, cfg config.RootConfig) *Reconciler {
	return &Reconciler{
		kubeClient:    client,
		logger:        logger,
		eventRecorder: recorder,
		config:        cfg,

		vpcpeeringRW: repository.NewVPCPeeringReaderWriter(client),
		dataPlaneRW:  repository.NewDataPlaneReaderWriter(client),
		vpcR:         repository.NewVPCReader(client),
		internetR:    repository.NewInternetReader(client),
	}
}

func (r *Reconciler) WithAWSClient(client aws.Client) *Reconciler {
	r.awsClient = client
	return r
}

func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := r.logger.WithValues("vpcpeering", req.NamespacedName)
	logger.Info("reconciling vpc peering")

	peering := &v1alpha1.VPCPeering{}
	if err := r.vpcpeeringRW.Get(ctx, req.NamespacedName, peering); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	// do nothing if the cr is paused
	if utils.IsPaused(peering.GetObjectMeta()) {
		logger.Info("reconciliation is paused either through the pause annotation",
			"annotation", v1alpha1.AnnotationKeyReconciliationPaused,
			"vpcpeering", req.NamespacedName.String(),
		)
		r.eventRecorder.Event(peering, "ReconciliationPaused", "reconciliation is paused through the pause annotation", v1alpha1.AnnotationKeyReconciliationPaused)
		return ctrl.Result{}, nil
	}

	var dp v1alpha1.DataPlane
	key := client.ObjectKey{
		Namespace: peering.Spec.DataPlane.Namespace,
		Name:      peering.Spec.DataPlane.Name,
	}
	if err := r.dataPlaneRW.Get(ctx, key, &dp); err != nil {
		if apierrors.IsNotFound(err) {
			logger.Info("dataplane CR not found", "dataplane", key.String())
		} else {
			logger.Error(err, "get dataplane CR failed", "dataplane", key.String())
		}
		return ctrl.Result{}, err
	}
	controllers.EnsureDataPlaneIDLabel(peering, dp.Name)

	if dp.Status.Vpc == nil {
		return ctrl.Result{}, fmt.Errorf("dataplane.status.vpc is nil")
	}
	var vpc v1alpha1.VPC
	key = client.ObjectKey{
		Name:      dp.Status.Vpc.Name,
		Namespace: dp.Status.Vpc.Namespace,
	}
	if err := r.vpcR.Get(ctx, key, &vpc); err != nil {
		if apierrors.IsNotFound(err) {
			logger.Info("vpc cr not found", "vpc", key.String())
		} else {
			logger.Error(err, "get vpc cr failed", "vpc", key.String())
		}
		return ctrl.Result{}, err
	}

	var internetList v1alpha1.InternetList
	if err := r.internetR.List(ctx, &internetList, client.InNamespace(dp.Namespace), client.MatchingLabels{
		v1alpha1.DataPlaneIDKey: dp.Name,
	}); err != nil {
		return ctrl.Result{}, err
	}
	if len(internetList.Items) != 1 {
		return ctrl.Result{}, fmt.Errorf("expected 1 internet for dataplane %s, found %d", dp.Name, len(internetList.Items))
	}

	c, err := (&task.Context{
		Local:      r.kubeClient,
		VPCPeering: peering,
		Dataplane:  &dp,
		VPC:        &vpc,
		Internet:   &internetList.Items[0],
		Config:     r.config,
	}).Default()
	if err != nil {
		logger.Error(err, "new task context failed")
		return ctrl.Result{}, fmt.Errorf("new task context failed: %v", err)
	}

	switch peering.Spec.Provider {
	case consts.ProviderTypeEKS, consts.ProviderTypeACK:
	default:
		logger.Info("reconciliation is skipped because provider is not supported",
			"provider", peering.Spec.Provider)
		return ctrl.Result{}, nil
	}

	tasks := BuildVPCPeeringTasks(c)
	d := deploy.NewDeployer(tasks, nil, logger)
	var res ctrl.Result
	if peering.DeletionTimestamp.IsZero() {
		res, err = updateCreate(ctx, c, d.Deploy(ctx))
	} else {
		if controllers.DeletionConfirmed(peering) {
			peering.Status.Phase = v1alpha1.VPCPeeringPhaseDeleting
			if err := r.vpcpeeringRW.UpdateStatus(ctx, peering); err != nil {
				return ctrl.Result{}, fmt.Errorf("failed to update peering status: %v", err)
			}
			res, err = updateDelete(ctx, c, d.Destroy(ctx))
		} else {
			logger.Info("waiting for vpcpeering deletion to be confirmed")
			return ctrl.Result{}, fmt.Errorf("vpcpeering deletion is not confirmed")
		}
	}
	if err == nil {
		r.eventRecorder.Event(peering, corev1.EventTypeNormal, "ReconcileSuccess", "")
	} else {
		r.eventRecorder.Event(peering, corev1.EventTypeWarning, "ReconcileFail", err.Error())
	}

	return res, err
}

func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, pds ...predicate.Predicate) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&v1alpha1.VPCPeering{}, builder.WithPredicates(pds...)).
		WithOptions(controller.Options{RateLimiter: k8s.RetryLimiter, MaxConcurrentReconciles: 5}).
		Complete(r)
}
