package v1

import (
	"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 accountlog = logf.Log.WithName("account-resource")

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

// TODO(user): EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!

//+kubebuilder:webhook:path=/mutate-cloud-pingcap-com-v1-account,mutating=true,failurePolicy=fail,sideEffects=None,groups=cloud.pingcap.com,resources=accounts,verbs=create;update,versions=v1,name=maccount.kb.io,admissionReviewVersions=v1

var _ webhook.Defaulter = &Account{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Account) Default() {
	accountlog.Info("default", "name", r.Name)

	// TODO(user): fill in your defaulting logic.
}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
//+kubebuilder:webhook:path=/validate-cloud-pingcap-com-v1-account,mutating=false,failurePolicy=fail,sideEffects=None,groups=cloud.pingcap.com,resources=accounts,verbs=create;update,versions=v1,name=vaccount.kb.io,admissionReviewVersions=v1

var _ webhook.Validator = &Account{}

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

	var allErrs field.ErrorList

	if err := validateProviderAccountConfig(*r); err != nil {
		allErrs = append(allErrs, err)
	}

	return nil, invalidOrNil(allErrs, r)
}

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

	var allErrs field.ErrorList
	oldAccount, _ := old.(*Account)

	if r.Spec.Provider != oldAccount.Spec.Provider {
		allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("provider"), r.Spec.Provider, "field is immutable"))
	}

	// AWS
	if r.Spec.AWS != nil && oldAccount.Spec.AWS != nil {
		if r.Spec.AWS.AccountID != oldAccount.Spec.AWS.AccountID {
			allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("aws").Child("accountID"), r.Spec.AWS.AccountID, "field is immutable"))
		}
		if r.Spec.AWS.AssumeRoleARN != oldAccount.Spec.AWS.AssumeRoleARN {
			allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("aws").Child("assumeRoleARN"), r.Spec.AWS.AssumeRoleARN, "field is immutable"))
		}
		if r.Spec.AWS.ExternalID != nil && oldAccount.Spec.AWS.ExternalID != nil {
			if *r.Spec.AWS.ExternalID != *oldAccount.Spec.AWS.ExternalID {
				allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("aws").Child("externalID"), r.Spec.AWS.ExternalID, "field is immutable"))
			}
		} else if r.Spec.AWS.ExternalID != oldAccount.Spec.AWS.ExternalID {
			allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("aws").Child("externalID"), r.Spec.AWS.ExternalID, "field is immutable"))
		}
	} else if r.Spec.AWS != oldAccount.Spec.AWS {
		// one of them is nil
		allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("aws"), r.Spec.AWS, "field is immutable"))
	}

	// GCP
	if r.Spec.GCP != nil && oldAccount.Spec.GCP != nil {
		if r.Spec.GCP.ProjectID != oldAccount.Spec.GCP.ProjectID {
			allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("gcp").Child("projectID"), r.Spec.GCP.ProjectID, "field is immutable"))
		}
		if r.Spec.GCP.ServiceAccount != oldAccount.Spec.GCP.ServiceAccount {
			allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("gcp").Child("serviceAccount"), r.Spec.GCP.ServiceAccount, "field is immutable"))
		}
	} else if r.Spec.GCP != oldAccount.Spec.GCP {
		// one of them is nil
		allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("gcp"), r.Spec.GCP, "field is immutable"))
	}

	return nil, invalidOrNil(allErrs, r)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *Account) ValidateDelete() (admission.Warnings, error) {
	accountlog.Info("validate delete", "name", r.Name)

	// TODO(user): fill in your validation logic upon object deletion.
	return nil, nil
}

// validateProviderAccountConfig validates if the cloud provider's account config is given.
func validateProviderAccountConfig(a Account) *field.Error {
	if a.Spec.Provider == CloudProviderAWS && a.Spec.AWS == nil {
		return field.Invalid(field.NewPath("spec").Child("aws"), a.Spec.AWS, "aws config is required for AWS provider")
	}

	if a.Spec.Provider == CloudProviderGCP && a.Spec.GCP == nil {
		return field.Invalid(field.NewPath("spec").Child("gcp"), a.Spec.GCP, "gcp config is required for GCP provider")
	}

	return nil
}
