package networkpolicy

import (
	"context"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/ec2"
	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	netv1 "k8s.io/api/networking/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/handler"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/manager"
	"sigs.k8s.io/controller-runtime/pkg/source"

	"github.com/tidbcloud/infra-provider/network-policy-controller/pkg/apply"
)

// Reconciler reconciles a NetworkPolicy object.
type Reconciler struct {
	Logger    logr.Logger
	Applier   apply.Applier
	AWSConfig *aws.Config

	ControlPlaneCIDRs []string
	LoadBalancerCIDRs []string
	O11yCIDRs         []string

	DNSNamespace   string
	DNSPodSelector *metav1.LabelSelector

	S3CIDRPoller *S3CIDRPoller
}

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)

	startTime := time.Now()
	isSkip := false
	defer func() {
		if isSkip {
			logger.Info("Skip reconcile",
				"time", time.Since(startTime),
			)

		} else {
			logger.Info("Finish reconcile",
				"time", time.Since(startTime),
			)
		}
	}()

	ns := corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: req.Namespace,
		},
	}

	err := r.Applier.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns)
	if err != nil {
		if apierrors.IsNotFound(err) {
			isSkip = true
			logger.Info("namespace has been deleted")
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, err
	}
	if !isTiDBNamespace(&ns) {
		isSkip = true
		return ctrl.Result{}, nil
	}

	if err := r.sync(ctx, req.Namespace, req.Name); err != nil {
		return ctrl.Result{}, err
	}

	return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr manager.Manager) error {
	c := ec2.NewFromConfig(*r.AWSConfig)
	r.Logger = mgr.GetLogger()
	r.S3CIDRPoller = NewS3CIDRPoller(r.Logger, r.Applier, c, r.AWSConfig.Region)

	return ctrl.NewControllerManagedBy(mgr).For(&netv1.NetworkPolicy{}).
		Watches(source.NewKindWithCache(&corev1.Namespace{}, mgr.GetCache()), &handler.Funcs{
			CreateFunc: r.handleCreateNamespaceEvent,
		}).
		// use empty handler because the req will be handled directly in poller
		Watches(r.S3CIDRPoller, handler.Funcs{}).
		Complete(r)
}
