package v1

import (
	"context"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"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 providerlog = logf.Log.WithName("provider-resource")

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

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

var _ webhook.Defaulter = &Provider{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Provider) Default() {
	if r.Labels == nil {
		r.Labels = map[string]string{}
	}
	if _, ok := r.Labels[ProviderTypeLabel]; !ok {
		r.Labels[ProviderTypeLabel] = r.Spec.Type
	}
}

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

var _ webhook.Validator = &Provider{}

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

	var allErrs field.ErrorList

	ptPath := field.NewPath("spec").Child("type")
	var pt ProviderType
	err := webhookClient.Get(context.TODO(), types.NamespacedName{Name: r.Spec.Type}, &pt)
	if err != nil {
		if apierrors.IsNotFound(err) {
			allErrs = append(allErrs, field.Invalid(ptPath, r.Spec.Type, "provider type is not declared"))
		}
		allErrs = append(allErrs, field.Invalid(ptPath, r.Spec.Type, err.Error()))
	}
	if err := r.validateProviderLabel(); err != nil {
		allErrs = append(allErrs, err)
	}

	allErrs = append(allErrs, r.validateGardener()...)

	return invalidOrNil(allErrs, r)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *Provider) ValidateUpdate(old runtime.Object) error {
	providerlog.V(4).Info("validate update", "name", r.Name)

	var allErrs field.ErrorList

	oldR := old.(*Provider)
	if oldR.Spec.Type != r.Spec.Type {
		allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("type"), r.Spec.Type, "field is immutable"))
	}
	if err := r.validateProviderLabel(); err != nil {
		allErrs = append(allErrs, err)
	}

	allErrs = append(allErrs, r.validateGardener()...)

	return invalidOrNil(allErrs, r)
}

func (r *Provider) validateGardener() field.ErrorList {
	switch r.Name {
	case "aws":
		fallthrough
	case "gcp":
		var allErrs field.ErrorList
		path := field.NewPath("spec").Child("forProvider")

		spec, err := GardenerProviderSpecForProvider(r)
		if err != nil {
			allErrs = append(allErrs, field.Invalid(path, r.Spec.ForProvider, err.Error()))
			return allErrs
		}

		switch r.Spec.Type {
		case "aws":
			if spec.AWS.AccountID == "" {
				allErrs = append(allErrs, field.Invalid(path.Child("aws").Child("accountID"), spec.AWS.AccountID, "account must be set"))
			}
			if spec.AWS.CrossplaneProvider == "" {
				allErrs = append(allErrs, field.Invalid(path.Child("aws").Child("crossplaneProvider"),
					spec.AWS.CrossplaneProvider, "crossplaneProvider must be set"))
			}
		case "gcp":
			if spec.GCP.ProjectID == "" {
				allErrs = append(allErrs, field.Invalid(path.Child("gcp").Child("project"), spec.GCP.ProjectID, "project must be set"))
			}
			if spec.AWS.AccountID == "" {
				allErrs = append(allErrs, field.Invalid(path.Child("aws").Child("accountID"), spec.AWS.AccountID, "aws account must be set"))
			}
			if spec.GCP.CertManagerRole == "" {
				allErrs = append(allErrs, field.Invalid(path.Child("gcpCertManagerRole"), spec.GCP.CertManagerRole, "gcpCertManagerRole must be set"))
			}
			if spec.GCP.CrossplaneProvider == "" {
				allErrs = append(allErrs, field.Invalid(path.Child("gcp").Child("crossplaneProvider"),
					spec.AWS.CrossplaneProvider, "gcp crossplaneProvider must be set"))
			}
			if spec.AWS.CrossplaneProvider == "" {
				allErrs = append(allErrs, field.Invalid(path.Child("aws").Child("crossplaneProvider"),
					spec.AWS.CrossplaneProvider, "crossplaneProvider must be set"))
			}
		}
		return allErrs
	default:
		return nil
	}
}

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

func (r *Provider) validateProviderLabel() *field.Error {
	if r.Labels[ProviderTypeLabel] != r.Spec.Type {
		return badProviderTypeLabel(r.Labels[ProviderTypeLabel], r.Spec.Type)
	}
	return nil
}
