// 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 instance_profile

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"strings"

	ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
	ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
	ackcondition "github.com/aws-controllers-k8s/runtime/pkg/condition"
	ackerr "github.com/aws-controllers-k8s/runtime/pkg/errors"
	ackrequeue "github.com/aws-controllers-k8s/runtime/pkg/requeue"
	ackrtlog "github.com/aws-controllers-k8s/runtime/pkg/runtime/log"
	"github.com/aws/aws-sdk-go-v2/aws"
	svcsdk "github.com/aws/aws-sdk-go-v2/service/iam"
	svcsdktypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
	smithy "github.com/aws/smithy-go"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

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

// Hack to avoid import errors during build...
var (
	_ = &metav1.Time{}
	_ = strings.ToLower("")
	_ = &svcsdk.Client{}
	_ = &svcapitypes.InstanceProfile{}
	_ = ackv1alpha1.AWSAccountID("")
	_ = &ackerr.NotFound
	_ = &ackcondition.NotManagedMessage
	_ = &reflect.Value{}
	_ = fmt.Sprintf("")
	_ = &ackrequeue.NoRequeue{}
	_ = &aws.Config{}
)

// sdkFind returns SDK-specific information about a supplied resource
func (rm *resourceManager) sdkFind(
	ctx context.Context,
	r *resource,
) (latest *resource, err error) {
	rlog := ackrtlog.FromContext(ctx)
	exit := rlog.Trace("rm.sdkFind")
	defer func() {
		exit(err)
	}()
	// If any required fields in the input shape are missing, AWS resource is
	// not created yet. Return NotFound here to indicate to callers that the
	// resource isn't yet created.
	if rm.requiredFieldsMissingFromReadOneInput(r) {
		return nil, ackerr.NotFound
	}

	input, err := rm.newDescribeRequestPayload(r)
	if err != nil {
		return nil, err
	}

	var resp *svcsdk.GetInstanceProfileOutput
	resp, err = rm.sdkapi.GetInstanceProfile(ctx, input)
	rm.metrics.RecordAPICall("READ_ONE", "GetInstanceProfile", err)
	if err != nil {
		var awsErr smithy.APIError
		if errors.As(err, &awsErr) && awsErr.ErrorCode() == "NoSuchEntity" {
			return nil, ackerr.NotFound
		}
		return nil, err
	}

	// Merge in the information we read from the API call above to the copy of
	// the original Kubernetes object we passed to the function
	ko := r.ko.DeepCopy()

	if ko.Status.ACKResourceMetadata == nil {
		ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}
	}
	if resp.InstanceProfile.Arn != nil {
		arn := ackv1alpha1.AWSResourceName(*resp.InstanceProfile.Arn)
		ko.Status.ACKResourceMetadata.ARN = &arn
	}
	if resp.InstanceProfile.CreateDate != nil {
		ko.Status.CreateDate = &metav1.Time{*resp.InstanceProfile.CreateDate}
	} else {
		ko.Status.CreateDate = nil
	}
	if resp.InstanceProfile.InstanceProfileId != nil {
		ko.Status.InstanceProfileID = resp.InstanceProfile.InstanceProfileId
	} else {
		ko.Status.InstanceProfileID = nil
	}
	if resp.InstanceProfile.InstanceProfileName != nil {
		ko.Spec.Name = resp.InstanceProfile.InstanceProfileName
	} else {
		ko.Spec.Name = nil
	}
	if resp.InstanceProfile.Path != nil {
		ko.Spec.Path = resp.InstanceProfile.Path
	} else {
		ko.Spec.Path = nil
	}
	if resp.InstanceProfile.Tags != nil {
		f5 := []*svcapitypes.Tag{}
		for _, f5iter := range resp.InstanceProfile.Tags {
			f5elem := &svcapitypes.Tag{}
			if f5iter.Key != nil {
				f5elem.Key = f5iter.Key
			}
			if f5iter.Value != nil {
				f5elem.Value = f5iter.Value
			}
			f5 = append(f5, f5elem)
		}
		ko.Spec.Tags = f5
	} else {
		ko.Spec.Tags = nil
	}

	rm.setStatusDefaults(ko)

	// Get the existing role associated with the instance profile. If the profile
	// has no role assigned, this field should be `nil`. This value is later
	// compared with the new desired value to ensure they are in sync.
	ko.Spec.Role = nil
	attachedRoles := resp.InstanceProfile.Roles
	if len(attachedRoles) > 0 {
		ko.Spec.Role = attachedRoles[0].RoleName
	}

	return &resource{ko}, nil
}

// requiredFieldsMissingFromReadOneInput returns true if there are any fields
// for the ReadOne Input shape that are required but not present in the
// resource's Spec or Status
func (rm *resourceManager) requiredFieldsMissingFromReadOneInput(
	r *resource,
) bool {
	return r.ko.Spec.Name == nil

}

// newDescribeRequestPayload returns SDK-specific struct for the HTTP request
// payload of the Describe API call for the resource
func (rm *resourceManager) newDescribeRequestPayload(
	r *resource,
) (*svcsdk.GetInstanceProfileInput, error) {
	res := &svcsdk.GetInstanceProfileInput{}

	if r.ko.Spec.Name != nil {
		res.InstanceProfileName = r.ko.Spec.Name
	}

	return res, nil
}

// sdkCreate creates the supplied resource in the backend AWS service API and
// returns a copy of the resource with resource fields (in both Spec and
// Status) filled in with values from the CREATE API operation's Output shape.
func (rm *resourceManager) sdkCreate(
	ctx context.Context,
	desired *resource,
) (created *resource, err error) {
	rlog := ackrtlog.FromContext(ctx)
	exit := rlog.Trace("rm.sdkCreate")
	defer func() {
		exit(err)
	}()
	input, err := rm.newCreateRequestPayload(ctx, desired)
	if err != nil {
		return nil, err
	}

	var resp *svcsdk.CreateInstanceProfileOutput
	_ = resp
	resp, err = rm.sdkapi.CreateInstanceProfile(ctx, input)
	rm.metrics.RecordAPICall("CREATE", "CreateInstanceProfile", err)
	if err != nil {
		return nil, err
	}
	// Merge in the information we read from the API call above to the copy of
	// the original Kubernetes object we passed to the function
	ko := desired.ko.DeepCopy()

	if ko.Status.ACKResourceMetadata == nil {
		ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}
	}
	if resp.InstanceProfile.Arn != nil {
		arn := ackv1alpha1.AWSResourceName(*resp.InstanceProfile.Arn)
		ko.Status.ACKResourceMetadata.ARN = &arn
	}
	if resp.InstanceProfile.CreateDate != nil {
		ko.Status.CreateDate = &metav1.Time{*resp.InstanceProfile.CreateDate}
	} else {
		ko.Status.CreateDate = nil
	}
	if resp.InstanceProfile.InstanceProfileId != nil {
		ko.Status.InstanceProfileID = resp.InstanceProfile.InstanceProfileId
	} else {
		ko.Status.InstanceProfileID = nil
	}
	if resp.InstanceProfile.InstanceProfileName != nil {
		ko.Spec.Name = resp.InstanceProfile.InstanceProfileName
	} else {
		ko.Spec.Name = nil
	}
	if resp.InstanceProfile.Path != nil {
		ko.Spec.Path = resp.InstanceProfile.Path
	} else {
		ko.Spec.Path = nil
	}
	if resp.InstanceProfile.Tags != nil {
		f5 := []*svcapitypes.Tag{}
		for _, f5iter := range resp.InstanceProfile.Tags {
			f5elem := &svcapitypes.Tag{}
			if f5iter.Key != nil {
				f5elem.Key = f5iter.Key
			}
			if f5iter.Value != nil {
				f5elem.Value = f5iter.Value
			}
			f5 = append(f5, f5elem)
		}
		ko.Spec.Tags = f5
	} else {
		ko.Spec.Tags = nil
	}

	rm.setStatusDefaults(ko)
	ackcondition.SetSynced(&resource{ko}, corev1.ConditionFalse, nil, nil)

	return &resource{ko}, nil
}

// newCreateRequestPayload returns an SDK-specific struct for the HTTP request
// payload of the Create API call for the resource
func (rm *resourceManager) newCreateRequestPayload(
	ctx context.Context,
	r *resource,
) (*svcsdk.CreateInstanceProfileInput, error) {
	res := &svcsdk.CreateInstanceProfileInput{}

	if r.ko.Spec.Name != nil {
		res.InstanceProfileName = r.ko.Spec.Name
	}
	if r.ko.Spec.Path != nil {
		res.Path = r.ko.Spec.Path
	}
	if r.ko.Spec.Tags != nil {
		f2 := []svcsdktypes.Tag{}
		for _, f2iter := range r.ko.Spec.Tags {
			f2elem := &svcsdktypes.Tag{}
			if f2iter.Key != nil {
				f2elem.Key = f2iter.Key
			}
			if f2iter.Value != nil {
				f2elem.Value = f2iter.Value
			}
			f2 = append(f2, *f2elem)
		}
		res.Tags = f2
	}

	return res, nil
}

// sdkUpdate patches the supplied resource in the backend AWS service API and
// returns a new resource with updated fields.
func (rm *resourceManager) sdkUpdate(
	ctx context.Context,
	desired *resource,
	latest *resource,
	delta *ackcompare.Delta,
) (*resource, error) {
	return rm.customUpdateInstanceProfile(ctx, desired, latest, delta)
}

// sdkDelete deletes the supplied resource in the backend AWS service API
func (rm *resourceManager) sdkDelete(
	ctx context.Context,
	r *resource,
) (latest *resource, err error) {
	rlog := ackrtlog.FromContext(ctx)
	exit := rlog.Trace("rm.sdkDelete")
	defer func() {
		exit(err)
	}()

	// All roles need to be deleted before the instance profile
	// can be removed
	if r.ko.Spec.Role != nil {
		if err = rm.detachRole(ctx, r); err != nil {
			return nil, err
		}
	}

	input, err := rm.newDeleteRequestPayload(r)
	if err != nil {
		return nil, err
	}
	var resp *svcsdk.DeleteInstanceProfileOutput
	_ = resp
	resp, err = rm.sdkapi.DeleteInstanceProfile(ctx, input)
	rm.metrics.RecordAPICall("DELETE", "DeleteInstanceProfile", err)
	return nil, err
}

// newDeleteRequestPayload returns an SDK-specific struct for the HTTP request
// payload of the Delete API call for the resource
func (rm *resourceManager) newDeleteRequestPayload(
	r *resource,
) (*svcsdk.DeleteInstanceProfileInput, error) {
	res := &svcsdk.DeleteInstanceProfileInput{}

	if r.ko.Spec.Name != nil {
		res.InstanceProfileName = r.ko.Spec.Name
	}

	return res, nil
}

// setStatusDefaults sets default properties into supplied custom resource
func (rm *resourceManager) setStatusDefaults(
	ko *svcapitypes.InstanceProfile,
) {
	if ko.Status.ACKResourceMetadata == nil {
		ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{}
	}
	if ko.Status.ACKResourceMetadata.Region == nil {
		ko.Status.ACKResourceMetadata.Region = &rm.awsRegion
	}
	if ko.Status.ACKResourceMetadata.OwnerAccountID == nil {
		ko.Status.ACKResourceMetadata.OwnerAccountID = &rm.awsAccountID
	}
	if ko.Status.Conditions == nil {
		ko.Status.Conditions = []*ackv1alpha1.Condition{}
	}
}

// updateConditions returns updated resource, true; if conditions were updated
// else it returns nil, false
func (rm *resourceManager) updateConditions(
	r *resource,
	onSuccess bool,
	err error,
) (*resource, bool) {
	ko := r.ko.DeepCopy()
	rm.setStatusDefaults(ko)

	// Terminal condition
	var terminalCondition *ackv1alpha1.Condition = nil
	var recoverableCondition *ackv1alpha1.Condition = nil
	var syncCondition *ackv1alpha1.Condition = nil
	for _, condition := range ko.Status.Conditions {
		if condition.Type == ackv1alpha1.ConditionTypeTerminal {
			terminalCondition = condition
		}
		if condition.Type == ackv1alpha1.ConditionTypeRecoverable {
			recoverableCondition = condition
		}
		if condition.Type == ackv1alpha1.ConditionTypeResourceSynced {
			syncCondition = condition
		}
	}
	var termError *ackerr.TerminalError
	if rm.terminalAWSError(err) || err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound || errors.As(err, &termError) {
		if terminalCondition == nil {
			terminalCondition = &ackv1alpha1.Condition{
				Type: ackv1alpha1.ConditionTypeTerminal,
			}
			ko.Status.Conditions = append(ko.Status.Conditions, terminalCondition)
		}
		var errorMessage = ""
		if err == ackerr.SecretTypeNotSupported || err == ackerr.SecretNotFound || errors.As(err, &termError) {
			errorMessage = err.Error()
		} else {
			awsErr, _ := ackerr.AWSError(err)
			errorMessage = awsErr.Error()
		}
		terminalCondition.Status = corev1.ConditionTrue
		terminalCondition.Message = &errorMessage
	} else {
		// Clear the terminal condition if no longer present
		if terminalCondition != nil {
			terminalCondition.Status = corev1.ConditionFalse
			terminalCondition.Message = nil
		}
		// Handling Recoverable Conditions
		if err != nil {
			if recoverableCondition == nil {
				// Add a new Condition containing a non-terminal error
				recoverableCondition = &ackv1alpha1.Condition{
					Type: ackv1alpha1.ConditionTypeRecoverable,
				}
				ko.Status.Conditions = append(ko.Status.Conditions, recoverableCondition)
			}
			recoverableCondition.Status = corev1.ConditionTrue
			awsErr, _ := ackerr.AWSError(err)
			errorMessage := err.Error()
			if awsErr != nil {
				errorMessage = awsErr.Error()
			}
			recoverableCondition.Message = &errorMessage
		} else if recoverableCondition != nil {
			recoverableCondition.Status = corev1.ConditionFalse
			recoverableCondition.Message = nil
		}
	}
	// Required to avoid the "declared but not used" error in the default case
	_ = syncCondition
	if terminalCondition != nil || recoverableCondition != nil || syncCondition != nil {
		return &resource{ko}, true // updated
	}
	return nil, false // not updated
}

// terminalAWSError returns awserr, true; if the supplied error is an aws Error type
// and if the exception indicates that it is a Terminal exception
// 'Terminal' exception are specified in generator configuration
func (rm *resourceManager) terminalAWSError(err error) bool {
	if err == nil {
		return false
	}

	var terminalErr smithy.APIError
	if !errors.As(err, &terminalErr) {
		return false
	}
	switch terminalErr.ErrorCode() {
	case "InvalidInput":
		return true
	default:
		return false
	}
}
