package controllers

import (
	"context"
	"time"

	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/predicate"
	gwv1 "sigs.k8s.io/gateway-api/apis/v1"

	anv1alpha1 "github.com/aws/aws-application-networking-k8s/pkg/apis/applicationnetworking/v1alpha1"
	pkg_aws "github.com/aws/aws-application-networking-k8s/pkg/aws"
	"github.com/aws/aws-application-networking-k8s/pkg/aws/services"
	"github.com/aws/aws-application-networking-k8s/pkg/controllers/predicates"
	deploy "github.com/aws/aws-application-networking-k8s/pkg/deploy/lattice"
	"github.com/aws/aws-application-networking-k8s/pkg/k8s"
	policy "github.com/aws/aws-application-networking-k8s/pkg/k8s/policyhelper"
	"github.com/aws/aws-application-networking-k8s/pkg/utils"
	"github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog"
)

type (
	VAP = anv1alpha1.VpcAssociationPolicy
)

const (
	finalizer = "vpcassociationpolicies.application-networking.k8s.aws/resources"
)

type vpcAssociationPolicyReconciler struct {
	log              gwlog.Logger
	client           client.Client
	cloud            pkg_aws.Cloud
	finalizerManager k8s.FinalizerManager
	manager          deploy.ServiceNetworkManager
	ph               *policy.PolicyHandler[*VAP]
}

func RegisterVpcAssociationPolicyController(log gwlog.Logger, cloud pkg_aws.Cloud, finalizerManager k8s.FinalizerManager, mgr ctrl.Manager) error {
	ph := policy.NewVpcAssociationPolicyHandler(log, mgr.GetClient())
	controller := &vpcAssociationPolicyReconciler{
		log:              log,
		client:           mgr.GetClient(),
		cloud:            cloud,
		finalizerManager: finalizerManager,
		manager:          deploy.NewDefaultServiceNetworkManager(log, cloud),
		ph:               ph,
	}

	b := ctrl.NewControllerManagedBy(mgr).
		For(&anv1alpha1.VpcAssociationPolicy{}, builder.WithPredicates(predicate.Or(predicate.GenerationChangedPredicate{}, predicates.AdditionalTagsAnnotationChangedPredicate)))
	ph.AddWatchers(b, &gwv1.Gateway{})
	return b.Complete(controller)
}

func (c *vpcAssociationPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	ctx = gwlog.StartReconcileTrace(ctx, c.log, "vpcassociationpolicy", req.Name, req.Namespace)
	defer func() {
		gwlog.EndReconcileTrace(ctx, c.log)
	}()

	k8sPolicy := &anv1alpha1.VpcAssociationPolicy{}
	err := c.client.Get(ctx, req.NamespacedName, k8sPolicy)
	if err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}
	c.log.Infow(ctx, "reconcile", "req", req, "targetRef", k8sPolicy.Spec.TargetRef)

	isDelete := !k8sPolicy.DeletionTimestamp.IsZero()
	isAssociation := k8sPolicy.Spec.AssociateWithVpc == nil || *k8sPolicy.Spec.AssociateWithVpc

	if isDelete || !isAssociation {
		err = c.delete(ctx, k8sPolicy)
	} else {
		err = c.upsert(ctx, k8sPolicy)
	}
	if err != nil {
		c.log.Infof(ctx, "reconcile error, retry in 30 sec: %s", err)
		return ctrl.Result{RequeueAfter: time.Second * 30}, nil
	}

	c.log.Infow(ctx, "reconciled vpc association policy",
		"req", req,
		"targetRef", k8sPolicy.Spec.TargetRef,
		"isDeleted", isDelete,
	)
	return ctrl.Result{}, nil
}

func (c *vpcAssociationPolicyReconciler) upsert(ctx context.Context, k8sPolicy *anv1alpha1.VpcAssociationPolicy) error {
	reason, err := c.ph.ValidateAndUpdateCondition(ctx, k8sPolicy)
	if err != nil {
		return err
	}
	if reason != policy.ReasonAccepted {
		return nil
	}

	err = c.finalizerManager.AddFinalizers(ctx, k8sPolicy, finalizer)
	if err != nil {
		return err
	}
	snName := string(k8sPolicy.Spec.TargetRef.Name)
	sgIds := utils.SliceMap(k8sPolicy.Spec.SecurityGroupIds, func(sg anv1alpha1.SecurityGroupId) *string {
		str := string(sg)
		return &str
	})

	additionalTags := k8s.GetAdditionalTagsFromAnnotations(ctx, k8sPolicy)

	snva, err := c.manager.UpsertVpcAssociation(ctx, snName, sgIds, additionalTags)
	if err != nil {
		return err
	}
	err = c.updateLatticeAnnotation(ctx, k8sPolicy, snva)
	if err != nil {
		return err
	}
	return nil
}

func (c *vpcAssociationPolicyReconciler) delete(ctx context.Context, k8sPolicy *anv1alpha1.VpcAssociationPolicy) error {
	snName := string(k8sPolicy.Spec.TargetRef.Name)
	err := c.manager.DeleteVpcAssociation(ctx, snName)
	if err != nil {
		return c.handleDeleteError(err)
	}
	err = c.finalizerManager.RemoveFinalizers(ctx, k8sPolicy, finalizer)
	if err != nil {
		return err
	}
	return nil
}

func (c *vpcAssociationPolicyReconciler) handleDeleteError(err error) error {
	switch {
	case services.IsNotFoundError(err):
		return nil
	}
	return err
}

func (c *vpcAssociationPolicyReconciler) updateLatticeAnnotation(ctx context.Context, k8sPolicy *anv1alpha1.VpcAssociationPolicy, resArn string) error {
	if k8sPolicy.Annotations == nil {
		k8sPolicy.Annotations = make(map[string]string)
	}
	k8sPolicy.Annotations["application-networking.k8s.aws/resourceArn"] = resArn
	err := c.client.Update(ctx, k8sPolicy)
	return err
}
