// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

// Code generated by ack-generate. DO NOT EDIT.

package db_cluster_endpoint

import (
	"context"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
	ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors"
	acktypes "github.com/aws-controllers-k8s/runtime/pkg/types"

	svcapitypes "github.com/aws-controllers-k8s/rds-controller/apis/v1alpha1"
)

// ClearResolvedReferences removes any reference values that were made
// concrete in the spec. It returns a copy of the input AWSResource which
// contains the original *Ref values, but none of their respective concrete
// values.
func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) acktypes.AWSResource {
	ko := rm.concreteResource(res).ko.DeepCopy()

	if ko.Spec.DBClusterIdentifierRef != nil {
		ko.Spec.DBClusterIdentifier = nil
	}

	return &resource{ko}
}

// ResolveReferences finds if there are any Reference field(s) present
// inside AWSResource passed in the parameter and attempts to resolve those
// reference field(s) into their respective target field(s). It returns a
// copy of the input AWSResource with resolved reference(s), a boolean which
// is set to true if the resource contains any references (regardless of if
// they are resolved successfully) and an error if the passed AWSResource's
// reference field(s) could not be resolved.
func (rm *resourceManager) ResolveReferences(
	ctx context.Context,
	apiReader client.Reader,
	res acktypes.AWSResource,
) (acktypes.AWSResource, bool, error) {
	ko := rm.concreteResource(res).ko

	resourceHasReferences := false
	err := validateReferenceFields(ko)
	if fieldHasReferences, err := rm.resolveReferenceForDBClusterIdentifier(ctx, apiReader, ko); err != nil {
		return &resource{ko}, (resourceHasReferences || fieldHasReferences), err
	} else {
		resourceHasReferences = resourceHasReferences || fieldHasReferences
	}

	return &resource{ko}, resourceHasReferences, err
}

// validateReferenceFields validates the reference field and corresponding
// identifier field.
func validateReferenceFields(ko *svcapitypes.DBClusterEndpoint) error {

	if ko.Spec.DBClusterIdentifierRef != nil && ko.Spec.DBClusterIdentifier != nil {
		return ackerr.ResourceReferenceAndIDNotSupportedFor("DBClusterIdentifier", "DBClusterIdentifierRef")
	}
	if ko.Spec.DBClusterIdentifierRef == nil && ko.Spec.DBClusterIdentifier == nil {
		return ackerr.ResourceReferenceOrIDRequiredFor("DBClusterIdentifier", "DBClusterIdentifierRef")
	}
	return nil
}

// resolveReferenceForDBClusterIdentifier reads the resource referenced
// from DBClusterIdentifierRef field and sets the DBClusterIdentifier
// from referenced resource. Returns a boolean indicating whether a reference
// contains references, or an error
func (rm *resourceManager) resolveReferenceForDBClusterIdentifier(
	ctx context.Context,
	apiReader client.Reader,
	ko *svcapitypes.DBClusterEndpoint,
) (hasReferences bool, err error) {
	if ko.Spec.DBClusterIdentifierRef != nil && ko.Spec.DBClusterIdentifierRef.From != nil {
		hasReferences = true
		arr := ko.Spec.DBClusterIdentifierRef.From
		if arr.Name == nil || *arr.Name == "" {
			return hasReferences, fmt.Errorf("provided resource reference is nil or empty: DBClusterIdentifierRef")
		}
		namespace := ko.ObjectMeta.GetNamespace()
		if arr.Namespace != nil && *arr.Namespace != "" {
			namespace = *arr.Namespace
		}
		obj := &svcapitypes.DBCluster{}
		if err := getReferencedResourceState_DBCluster(ctx, apiReader, obj, *arr.Name, namespace); err != nil {
			return hasReferences, err
		}
		ko.Spec.DBClusterIdentifier = (*string)(obj.Spec.DBClusterIdentifier)
	}

	return hasReferences, nil
}

// getReferencedResourceState_DBCluster looks up whether a referenced resource
// exists and is in a ACK.ResourceSynced=True state. If the referenced resource does exist and is
// in a Synced state, returns nil, otherwise returns `ackerr.ResourceReferenceTerminalFor` or
// `ResourceReferenceNotSyncedFor` depending on if the resource is in a Terminal state.
func getReferencedResourceState_DBCluster(
	ctx context.Context,
	apiReader client.Reader,
	obj *svcapitypes.DBCluster,
	name string, // the Kubernetes name of the referenced resource
	namespace string, // the Kubernetes namespace of the referenced resource
) error {
	namespacedName := types.NamespacedName{
		Namespace: namespace,
		Name:      name,
	}
	err := apiReader.Get(ctx, namespacedName, obj)
	if err != nil {
		return err
	}
	var refResourceTerminal bool
	for _, cond := range obj.Status.Conditions {
		if cond.Type == ackv1alpha1.ConditionTypeTerminal &&
			cond.Status == corev1.ConditionTrue {
			return ackerr.ResourceReferenceTerminalFor(
				"DBCluster",
				namespace, name)
		}
	}
	if refResourceTerminal {
		return ackerr.ResourceReferenceTerminalFor(
			"DBCluster",
			namespace, name)
	}
	var refResourceSynced bool
	for _, cond := range obj.Status.Conditions {
		if cond.Type == ackv1alpha1.ConditionTypeResourceSynced &&
			cond.Status == corev1.ConditionTrue {
			refResourceSynced = true
		}
	}
	if !refResourceSynced {
		return ackerr.ResourceReferenceNotSyncedFor(
			"DBCluster",
			namespace, name)
	}
	if obj.Spec.DBClusterIdentifier == nil {
		return ackerr.ResourceReferenceMissingTargetFieldFor(
			"DBCluster",
			namespace, name,
			"Spec.DBClusterIdentifier")
	}
	return nil
}
