package v1

import (
	"context"
	"fmt"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/validation/field"
	ctrl "sigs.k8s.io/controller-runtime"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/webhook"
)

// log is for logging in this package.
var networkpeerlog = logf.Log.WithName("networkpeer-resource")

func (r *NetworkPeer) setupWebhookWithManager(mgr ctrl.Manager) error {
	return ctrl.NewWebhookManagedBy(mgr).
		For(r).
		Complete()
}

//+kubebuilder:webhook:path=/mutate-cloud-pingcap-com-v1-networkpeer,mutating=true,failurePolicy=fail,sideEffects=None,groups=cloud.pingcap.com,resources=networkpeers,verbs=create;update,versions=v1,name=mnetworkpeer.kb.io,admissionReviewVersions={v1,v1beta1}

var _ webhook.Defaulter = &NetworkPeer{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *NetworkPeer) Default() {
	if r.Labels == nil {
		r.Labels = map[string]string{}
	}

	var nt Network
	err := webhookClient.Get(context.TODO(), types.NamespacedName{
		Namespace: r.Namespace,
		Name:      r.Spec.Source,
	}, &nt)
	if err != nil {
		networklog.Error(err, "error get network of cluster, forward to validation")
		return
	}
	// network must have a type label
	r.Labels[ProviderTypeLabel] = nt.Labels[ProviderTypeLabel]
	r.Labels[RegionLabel] = nt.Spec.Region
}

//+kubebuilder:webhook:path=/validate-cloud-pingcap-com-v1-networkpeer,mutating=false,failurePolicy=fail,sideEffects=None,groups=cloud.pingcap.com,resources=networkpeers,verbs=create;update,versions=v1,name=vnetworkpeer.kb.io,admissionReviewVersions={v1,v1beta1}

var _ webhook.Validator = &NetworkPeer{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *NetworkPeer) ValidateCreate() error {
	networkpeerlog.V(4).Info("validate create", "name", r.Name)

	var allErrs field.ErrorList
	if errs := r.ValidateSpec(); errs != nil {
		allErrs = append(allErrs, errs...)
	}

	return invalidOrNil(allErrs, r)
}

func (r *NetworkPeer) ValidateSpec() field.ErrorList {
	var allErrs field.ErrorList
	fld := field.NewPath("spec")

	var sn, tn Network
	var targets []string

	err := webhookClient.Get(context.TODO(), types.NamespacedName{Name: r.Spec.Source, Namespace: r.ObjectMeta.Namespace}, &sn)
	if err != nil {
		allErrs = append(allErrs, field.Invalid(fld.Child("source"), r.Spec.Source,
			fmt.Sprintf("source network not found, err: %s", err.Error())))
		return allErrs
	}

	if r.Spec.Target.AWS != nil {
		targets = append(targets, ProviderTypeAWS)
		overlap, err := checkOverlap(sn.Spec.CIDR, r.Spec.Target.AWS.PeerCIDR)
		if err != nil {
			allErrs = append(allErrs, field.Invalid(fld, r.Spec, fmt.Sprintf("Parsing CIDR error: %s", err)))
			return allErrs
		}
		if overlap {
			allErrs = append(allErrs, field.Invalid(fld, r.Spec, "source and target networks are overlapped"))
			return allErrs
		}
	}
	if r.Spec.Target.GCP != nil {
		targets = append(targets, ProviderTypeGCP)
	}

	if r.Spec.Target.TargetNetwork != nil {
		err = webhookClient.Get(context.TODO(), types.NamespacedName{Name: r.Spec.Target.TargetNetwork.NetworkName, Namespace: r.ObjectMeta.Namespace}, &tn)
		if err != nil {
			allErrs = append(allErrs, field.Invalid(fld.Child("target.network.networkName"), r.Spec.Target.TargetNetwork,
				fmt.Sprintf("target network not found, err: %s", err.Error())))
			return allErrs
		}
		targets = append(targets, tn.Spec.Provider)
	}

	if len(targets) == 0 {
		allErrs = append(allErrs, field.Invalid(fld.Child("target"), r.Spec.Target, "at least one target network has to be specified"))
		return allErrs
	}

	if len(targets) > 1 {
		allErrs = append(allErrs, field.Invalid(fld.Child("target"), r.Spec.Target, "the target network is ambigiouous"))
		return allErrs
	}

	targetType := targets[0]

	// peering support aws to aws cloud provider or gcp to gcp cloud provider
	// eks provider else belongs to aws cloud provider
	if cloudProvider(targetType) != cloudProvider(sn.Spec.Provider) {
		allErrs = append(allErrs, field.Invalid(fld.Child("target"), r.Spec.Target, fmt.Sprintf("peering from %s to %s is not supported", CloudProvider(sn.Spec.Provider), CloudProvider(targetType))))
		return allErrs
	}

	return allErrs
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *NetworkPeer) ValidateUpdate(old runtime.Object) error {
	return nil
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *NetworkPeer) ValidateDelete() error {
	return nil
}

// cloudProvider returns the cloud provider of the provider type.
// gardener aws provider and eks provider is belong to aws cloud provider
func cloudProvider(providerType string) string {
	switch providerType {
	case ProviderTypeAWS, ProviderTypeEKS:
		return CloudProviderAWS
	case ProviderTypeGCP:
		return CloudProviderGCP
	}

	return ""
}
