/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package software.amazon.greengrassv2.deployment;

import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import software.amazon.awssdk.arns.Arn;
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.services.greengrassv2.GreengrassV2Client;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ProxyClient;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;

import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class UpdateHandler extends BaseHandlerStd {
    private Logger logger;
    private static final String NOT_UPDATABLE_MESSAGE_FMT = "%s property cannot be updated.";
    protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
            final AmazonWebServicesClientProxy proxy,
            final ResourceHandlerRequest<ResourceModel> request,
            final CallbackContext callbackContext,
            final ProxyClient<GreengrassV2Client> proxyClient,
            final Logger logger) {

        this.logger = logger;

        final String deploymentArn = getDeploymentArnFromRequest(request);

        return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
                .then(this::validateDeploymentIdPresent)
                .then(progressEvent -> validateIfDeploymentResourceExists(proxyClient, progressEvent))
                .then(progressEvent -> validateCreateOnlyPropertiesNotChanged(
                        request.getDesiredResourceState(), request.getPreviousResourceState(), progressEvent))
                .then(progress -> updateTags(proxy, proxyClient, deploymentArn,
                        request.getDesiredResourceTags(), request.getPreviousResourceTags(), progress))
                .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger));
    }

    private ProgressEvent<ResourceModel, CallbackContext> validateIfDeploymentResourceExists(final ProxyClient<GreengrassV2Client> proxyClient,
                                                                                             ProgressEvent<ResourceModel, CallbackContext> progressEvent) {
        try {
            proxyClient.injectCredentialsAndInvokeV2(Translator.translateToReadRequest(progressEvent.getResourceModel()),
                    proxyClient.client()::getDeployment);
        } catch (final AwsServiceException e) {
            throw ExceptionTranslator.translateToCfnException("UpdateDeployment", e);
        }
        return progressEvent;
    }

    private ProgressEvent<ResourceModel, CallbackContext> validateCreateOnlyPropertiesNotChanged(
            ResourceModel desiredResourceState,
            ResourceModel previousResourceState,
            ProgressEvent<ResourceModel, CallbackContext> progressEvent) {

        if (!Objects.equals(desiredResourceState.getTargetArn(), previousResourceState.getTargetArn())) {
            return ProgressEvent.failed(previousResourceState, progressEvent.getCallbackContext(),
                    HandlerErrorCode.NotUpdatable, String.format(NOT_UPDATABLE_MESSAGE_FMT, "TargetArn"));
        }

        if (!Objects.equals(desiredResourceState.getParentTargetArn(), previousResourceState.getParentTargetArn())) {
            return ProgressEvent.failed(previousResourceState, progressEvent.getCallbackContext(),
                    HandlerErrorCode.NotUpdatable, String.format(NOT_UPDATABLE_MESSAGE_FMT, "ParentTargetArn"));
        }

        if (!Objects.equals(desiredResourceState.getComponents(), previousResourceState.getComponents())) {
            return ProgressEvent.failed(previousResourceState, progressEvent.getCallbackContext(),
                    HandlerErrorCode.NotUpdatable, String.format(NOT_UPDATABLE_MESSAGE_FMT, "Component"));
        }

        if (!Objects.equals(desiredResourceState.getIotJobConfiguration(), previousResourceState.getIotJobConfiguration())) {
            return ProgressEvent.failed(previousResourceState, progressEvent.getCallbackContext(),
                    HandlerErrorCode.NotUpdatable, String.format(NOT_UPDATABLE_MESSAGE_FMT, "IotJobConfiguration"));
        }

        if (!Objects.equals(desiredResourceState.getDeploymentName(), previousResourceState.getDeploymentName())) {
            return ProgressEvent.failed(previousResourceState, progressEvent.getCallbackContext(),
                    HandlerErrorCode.NotUpdatable, String.format(NOT_UPDATABLE_MESSAGE_FMT, "DeploymentName"));
        }

        if (!Objects.equals(desiredResourceState.getDeploymentPolicies(), previousResourceState.getDeploymentPolicies())) {
            return ProgressEvent.failed(previousResourceState, progressEvent.getCallbackContext(),
                    HandlerErrorCode.NotUpdatable, String.format(NOT_UPDATABLE_MESSAGE_FMT, "DeploymentPolicies"));
        }

        return progressEvent;
    }

    private ProgressEvent<ResourceModel, CallbackContext> updateTags(
            AmazonWebServicesClientProxy proxy,
            ProxyClient<GreengrassV2Client> proxyClient,
            String deploymentArn,
            Map<String, String> desiredResourceTags,
            Map<String, String> previousResourceTags,
            ProgressEvent<ResourceModel, CallbackContext> progress) {

        final MapDifference<String, String> difference = Maps.difference(desiredResourceTags, previousResourceTags);
        final Map<String, String> tagsToUpdate = desiredResourceTags.entrySet().stream().filter(e->difference.entriesDiffering().containsKey(e.getKey())).collect(
                Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        final Map<String, String> tagsToAdd = difference.entriesOnlyOnLeft();
        tagsToUpdate.putAll(tagsToAdd);
        final Map<String, String> tagsToRemove = difference.entriesOnlyOnRight();

        return progress
                .then(progressEvent -> {
                    if (!tagsToUpdate.isEmpty()) {
                        return proxy.initiate("AWS-GreengrassV2-Deployment::Update::AddTags", proxyClient, progressEvent.getResourceModel(), progress.getCallbackContext())
                                .translateToServiceRequest(model -> Translator.translateToTagResourceRequest(deploymentArn, tagsToUpdate))
                                .makeServiceCall((awsRequest, client) -> {
                                    try {
                                        return client.injectCredentialsAndInvokeV2(awsRequest, client.client()::tagResource);
                                    } catch (Exception ex) {
                                        throw ExceptionTranslator.translateToCfnExceptionForCreatedResource("TagResource", deploymentArn, ex);
                                    }
                                })
                                .progress();
                    } else {
                        return progress;
                    }
                })
                .then(progressEvent ->{
                    if (!tagsToRemove.isEmpty()) {
                        return proxy.initiate("AWS-GreengrassV2-Deployment::Update::RemoveTags", proxyClient, progressEvent.getResourceModel(), progress.getCallbackContext())
                                .translateToServiceRequest(model -> Translator.translateToUntagResourceRequest(deploymentArn, tagsToRemove))
                                .makeServiceCall((awsRequest, client) -> {
                                    try {
                                        return client.injectCredentialsAndInvokeV2(awsRequest, client.client()::untagResource);
                                    } catch (Exception ex) {
                                        throw ExceptionTranslator.translateToCfnExceptionForCreatedResource("UntagResource", deploymentArn, ex);
                                    }
                                })
                                .progress();
                    } else {
                        return progress;
                    }
                });
    }

    private static String getDeploymentArnFromRequest(final ResourceHandlerRequest<ResourceModel> request) {
        return Arn.builder()
                .partition(request.getAwsPartition())
                .region(request.getRegion())
                .accountId(request.getAwsAccountId())
                .service("greengrass")
                .resource("deployments:" + request.getDesiredResourceState().getDeploymentId())
                .build()
                .toString();
    }
}