package software.amazon.imagebuilder.imagepipeline;

import com.google.common.base.Strings;
import software.amazon.awssdk.services.imagebuilder.model.ResourceNotFoundException;
import software.amazon.awssdk.services.imagebuilder.model.UpdateImagePipelineResponse;
import software.amazon.cloudformation.exceptions.CfnNotFoundException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


public class UpdateHandler extends BaseHandler<CallbackContext> {
    private AmazonWebServicesClientProxy clientProxy;
    private Logger logger;

    @Override
    public ProgressEvent<ResourceModel, CallbackContext> handleRequest(
            final AmazonWebServicesClientProxy proxy,
            final ResourceHandlerRequest<ResourceModel> request,
            final CallbackContext callbackContext,
            final Logger logger) {
        this.clientProxy = proxy;
        this.logger = logger;
        final ResourceModel currentModel = request.getDesiredResourceState();
        final ResourceModel previousModel = request.getPreviousResourceState();
        final String arn = previousModel.getArn();

        UpdateImagePipelineResponse response;

        if (Strings.isNullOrEmpty(arn)) throw new CfnNotFoundException(ResourceModel.TYPE_NAME,
                "Not able to update stack because no ARN found from model");

        try {
            response = proxy.injectCredentialsAndInvokeV2(RequestUtil.generateUpdateImagePipelineRequest(arn, currentModel),
                    ClientBuilder.getImageBuilderClient()::updateImagePipeline);

            Map<String, String> previousTagMap = previousModel.getTags() == null ? new HashMap<>() : previousModel.getTags();
            Map<String, String> currentTagMap = currentModel.getTags() == null ? new HashMap<>() : currentModel.getTags();

            // No tag/untag operation when no tag in both previous and current model.
            if (previousTagMap.isEmpty() && currentTagMap.isEmpty()) {
                //No operation
            }
            else if (previousTagMap.isEmpty()) {
                // Tag all resource if no tag in previous tag map
                proxy.injectCredentialsAndInvokeV2(RequestUtil.generateTagImagePipelineRequest(
                        arn, currentTagMap),
                        ClientBuilder.getImageBuilderClient()::tagResource);

            } else if (currentTagMap.isEmpty()) {
                // Untag all resource if no tag in current tag map
                List<String> keyList = new LinkedList<>();
                for (Map.Entry<String, String> previousTagMapEntry : previousTagMap.entrySet()) {
                    keyList.add(previousTagMapEntry.getKey());
                }
                proxy.injectCredentialsAndInvokeV2(RequestUtil.generateUntagImagePipelineRequest(arn, keyList),
                        ClientBuilder.getImageBuilderClient()::untagResource);
            } else {
                // Untag all resource tags which are not in the updatedTagMap provided by customer.
                List<String> untagKeyList = new LinkedList<>();
                for (Map.Entry<String,String> previousTagMapEntry : previousTagMap.entrySet()) {
                    if (! currentTagMap.containsKey(previousTagMapEntry.getKey())) {
                        untagKeyList.add(previousTagMapEntry.getKey());
                    }
                }
                if (!untagKeyList.isEmpty()) {
                    proxy.injectCredentialsAndInvokeV2(RequestUtil.generateUntagImagePipelineRequest(arn, untagKeyList),
                            ClientBuilder.getImageBuilderClient()::untagResource);
                }

                // Tag resource with new tags with new keys.
                Map<String, String> tagKeyMap = new HashMap<>();
                for (Map.Entry<String,String> currentTagMapEntry : currentTagMap.entrySet()) {
                    if (! previousTagMap.containsKey(currentTagMapEntry.getKey())) {
                        tagKeyMap.put(currentTagMapEntry.getKey(), currentTagMapEntry.getValue());
                    }
                }
                if (!tagKeyMap.isEmpty()) {
                    proxy.injectCredentialsAndInvokeV2(RequestUtil.generateTagImagePipelineRequest(arn, tagKeyMap),
                            ClientBuilder.getImageBuilderClient()::tagResource);
                }

                // Update Tag when tag keys exist but tag values updated.
                Map<String, String> updateKeyMap = new HashMap<>();
                for (Map.Entry<String,String> currentTagMapEntry : currentTagMap.entrySet()) {
                    String currentTagEntryKey = currentTagMapEntry.getKey();
                    String currentTagEntryValue = currentTagMapEntry.getValue();

                    if (previousTagMap.containsKey(currentTagEntryKey) && ! previousTagMap.get(currentTagEntryKey).equals(currentTagEntryValue)) {
                        updateKeyMap.put(currentTagMapEntry.getKey(), currentTagMapEntry.getValue());
                    }
                }
                if (!updateKeyMap.isEmpty()) {
                    proxy.injectCredentialsAndInvokeV2(RequestUtil.generateTagImagePipelineRequest(arn, updateKeyMap),
                            ClientBuilder.getImageBuilderClient()::tagResource);
                }
            }

            currentModel.setArn(arn);

        } catch (ResourceNotFoundException e) {
            throw new CfnNotFoundException(ResourceModel.TYPE_NAME, arn);
        }

        return ProgressEvent.<ResourceModel, CallbackContext>builder()
                .resourceModel(currentModel)
                .status(OperationStatus.SUCCESS)
                .build();
    }
}