package v1

import (
	"fmt"

	"k8s.io/apimachinery/pkg/runtime"
	"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"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

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

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

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

var _ webhook.Defaulter = &Branch{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Branch) Default() {
	if r.Spec.ForProvider == nil {
		r.Spec.ForProvider = &runtime.RawExtension{}
	}
	populateRawExtension(r.Spec.ForProvider)
	populateRawExtension(&r.Status.FromProvider)
}

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

var _ webhook.Validator = &Branch{}

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

	var allErrs field.ErrorList
	if r.GetLabels()[ProviderTypeLabel] == "" {
		allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("labels"), r.GetLabels()[ProviderTypeLabel],
			fmt.Sprintf("%s label is missing", ProviderTypeLabel)))
	}
	if r.GetLabels()[LabelKubernetes] == "" {
		allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("labels"), r.GetLabels()[LabelKubernetes],
			fmt.Sprintf("%s label is missing", LabelKubernetes)))
	}
	if errs := r.validateSqlEndpoints(field.NewPath("spec").Child("sqlEndpoints"), r.Spec.SqlEndpoints); errs != nil {
		allErrs = append(allErrs, errs...)
	}

	return nil, invalidOrNil(allErrs, r)
}

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

	var allErrs field.ErrorList
	oldR := old.(*Branch)

	if r.GetLabels()[ProviderTypeLabel] != oldR.GetLabels()[ProviderTypeLabel] {
		allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("labels"), r.GetLabels()[ProviderTypeLabel],
			fmt.Sprintf("%s label is immutable", ProviderTypeLabel)))
	}
	if r.GetLabels()[LabelKubernetes] != oldR.GetLabels()[LabelKubernetes] {
		allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("labels"), r.GetLabels()[LabelKubernetes],
			fmt.Sprintf("%s label is immutable", LabelKubernetes)))
	}

	if oldR.Spec.SqlEndpoints.DNS.Identity != "" && r.Spec.SqlEndpoints.DNS.Identity != oldR.Spec.SqlEndpoints.DNS.Identity {
		allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("sqlEndpoints.dns.identity"),
			r.Spec.SqlEndpoints.DNS.Identity, "field is immutable after it is set"))
	}
	if r.Spec.SqlEndpoints.Public.IsEnabled() && oldR.Spec.SqlEndpoints.Public.IsEnabled() {
		if r.GetPublicSQLEndpointPort() != oldR.GetPublicSQLEndpointPort() {
			allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("sqlEndpoints.public.port"),
				r.Spec.SqlEndpoints.Public.Port, "field is immutable"))
		}
	}
	if r.Spec.SqlEndpoints.Private != nil && oldR.Spec.SqlEndpoints.Private != nil {
		if r.Spec.SqlEndpoints.Private.IsEnabled() && oldR.Spec.SqlEndpoints.Private.IsEnabled() {
			if r.GetPrivateSQLEndpointPort() != oldR.GetPrivateSQLEndpointPort() {
				allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("sqlEndpoints.private.port"),
					r.Spec.SqlEndpoints.Private.Port, "field is immutable"))
			}
		}
	}
	if errs := r.validateSqlEndpoints(field.NewPath("spec").Child("sqlEndpoints"), r.Spec.SqlEndpoints); errs != nil {
		allErrs = append(allErrs, errs...)
	}

	// serverless label LabelEncryptKeyspace is immutable
	if r.Labels[ProviderTypeLabel] == ProviderTypeAWSFreeTier {
		if r.Labels[LabelEncryptKeyspace] != oldR.Labels[LabelEncryptKeyspace] {
			allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("labels").Child(LabelEncryptKeyspace),
				r.Labels[LabelEncryptKeyspace], "field is immutable"))
		}
	}
	return nil, invalidOrNil(allErrs, r)
}

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

func (r *Branch) validateSqlEndpoints(parent *field.Path, endpoints BranchSqlEndpointsSpec) field.ErrorList {
	var allErrs field.ErrorList
	if endpoints.Public.Port != nil && *endpoints.Public.Port == 10080 {
		allErrs = append(allErrs, field.Invalid(parent.Child("public.port"), *endpoints.Public.Port, "port is reserved"))
	}
	if endpoints.Private != nil && endpoints.Private.Port != nil && *endpoints.Private.Port == 10080 {
		allErrs = append(allErrs, field.Invalid(parent.Child("private.port"), *endpoints.Private.Port, "port is reserved"))
	}
	return allErrs
}
