package com.amazonaws.accessanalyzer.analyzer;

import static com.amazonaws.accessanalyzer.analyzer.TestUtil.ANALYZER_ARN;
import static com.amazonaws.accessanalyzer.analyzer.TestUtil.ANALYZER_NAME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;

import com.amazonaws.AmazonServiceException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import lombok.val;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import software.amazon.awssdk.awscore.AwsRequest;
import software.amazon.awssdk.services.accessanalyzer.model.CreateArchiveRuleRequest;
import software.amazon.awssdk.services.accessanalyzer.model.CreateArchiveRuleResponse;
import software.amazon.awssdk.services.accessanalyzer.model.DeleteArchiveRuleRequest;
import software.amazon.awssdk.services.accessanalyzer.model.DeleteArchiveRuleResponse;
import software.amazon.awssdk.services.accessanalyzer.model.ServiceQuotaExceededException;
import software.amazon.awssdk.services.accessanalyzer.model.TagResourceRequest;
import software.amazon.awssdk.services.accessanalyzer.model.TagResourceResponse;
import software.amazon.awssdk.services.accessanalyzer.model.UntagResourceRequest;
import software.amazon.awssdk.services.accessanalyzer.model.UntagResourceResponse;
import software.amazon.awssdk.services.accessanalyzer.model.UpdateArchiveRuleRequest;
import software.amazon.awssdk.services.accessanalyzer.model.UpdateArchiveRuleResponse;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;

class UpdateHandlerTest {

  @Mock
  private AmazonWebServicesClientProxy proxy;

  @Mock
  private Logger logger;

  @BeforeEach
  void setup() {
    proxy = mock(AmazonWebServicesClientProxy.class);
    logger = mock(Logger.class);
  }

  @Test
  void testBasicTags() {
    val desiredModel = ResourceModel
        .builder()
        .analyzerName(ANALYZER_NAME)
        .arn(ANALYZER_ARN)
        .type(TestUtil.ACCOUNT)
        .tags(
            ImmutableSet.of(
                Tag.builder().key("c").value("3").build(), // new
                Tag.builder().key("b").value("2").build(), // update
                Tag.builder().key("a").value("1").build()  // ignore
            )
        )
        .build();

    val previousModel = ResourceModel
        .builder()
        .analyzerName(ANALYZER_NAME)
        .arn(ANALYZER_ARN)
        .type(TestUtil.ACCOUNT)
        .tags(
            ImmutableSet.of(
                Tag.builder().key("a").value("1").build(),
                Tag.builder().key("b").value("7").build(),
                Tag.builder().key("z").value("5").build()  // delete
            )
        )
        .build();

    val request = ResourceHandlerRequest.<ResourceModel>builder()
        .desiredResourceState(desiredModel)
        .previousResourceState(previousModel)
        .build();

    val captor = ArgumentCaptor.forClass(AwsRequest.class);

    doReturn(UntagResourceResponse.builder().build()) // delete z
        .doReturn(TagResourceResponse.builder().build()) // add b, c
        .when(proxy)
        .injectCredentialsAndInvokeV2(captor.capture(), any());

    val response = invokeHandleRequest(request);

    assertThat(response).isNotNull();
    assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
    assertThat(response.getCallbackContext()).isNull();
    assertThat(response.getResourceModels()).isNull();
    assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
    assertThat(response.getMessage()).isNull();
    assertThat(response.getErrorCode()).isNull();

    assertThat(captor.getAllValues().size()).isEqualTo(2);
    val untagRequest = (UntagResourceRequest) captor.getAllValues().get(0);
    val tagRequest = (TagResourceRequest) captor.getAllValues().get(1);
    assertThat(untagRequest.tagKeys().equals(Collections.singletonList("z")));
    assertThat(tagRequest.tags().equals(ImmutableMap.of("b", "2", "c", "3")));
  }

  @Test
  void testBasicRules() {
    val desiredModel = ResourceModel
        .builder()
        .analyzerName(ANALYZER_NAME)
        .arn(ANALYZER_ARN)
        .type(TestUtil.ACCOUNT)
        .archiveRules(
            ImmutableList.of(
                ArchiveRule.builder().ruleName("c")
                    .filter(ImmutableList.of(Filter.builder().property("3").build())).build(), // new
                ArchiveRule.builder().ruleName("b")
                    .filter(ImmutableList.of(Filter.builder().property("2").build())).build(), // update
                ArchiveRule.builder().ruleName("a")
                    .filter(ImmutableList.of(Filter.builder().property("1").build())).build() // ignore
            )
        )
        .build();

    val previousModel = ResourceModel
        .builder()
        .analyzerName(ANALYZER_NAME)
        .arn(ANALYZER_ARN)
        .type(TestUtil.ACCOUNT)
        .archiveRules(
            ImmutableList.of(
                ArchiveRule.builder().ruleName("a")
                    .filter(ImmutableList.of(Filter.builder().property("1").build())).build(),
                ArchiveRule.builder().ruleName("b")
                    .filter(ImmutableList.of(Filter.builder().property("7").build())).build(),
                ArchiveRule.builder().ruleName("z")
                    .filter(ImmutableList.of(Filter.builder().property("5").build())).build() // delete
            )
        )
        .build();

    val request = ResourceHandlerRequest.<ResourceModel>builder()
        .desiredResourceState(desiredModel)
        .previousResourceState(previousModel)
        .build();

    val captor = ArgumentCaptor.forClass(AwsRequest.class);

    doReturn(DeleteArchiveRuleResponse.builder().build()) // delete z
        .doReturn(CreateArchiveRuleResponse.builder().build()) // add c
        .doReturn(UpdateArchiveRuleResponse.builder().build()) // update b
        .when(proxy)
        .injectCredentialsAndInvokeV2(captor.capture(), any());

    val response = invokeHandleRequest(request);

    assertThat(response).isNotNull();
    assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
    assertThat(response.getCallbackContext()).isNull();
    assertThat(response.getResourceModels()).isNull();
    assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
    assertThat(response.getMessage()).isNull();
    assertThat(response.getErrorCode()).isNull();

    assertThat(captor.getAllValues().size()).isEqualTo(3);
    val deleteRequest = (DeleteArchiveRuleRequest) captor.getAllValues().get(0);
    val createRequest = (CreateArchiveRuleRequest) captor.getAllValues().get(1);
    val updateRequest = (UpdateArchiveRuleRequest) captor.getAllValues().get(2);
    assertThat(deleteRequest.ruleName().equals("z"));
    assertThat(createRequest.ruleName().equals("c"));
    assertThat(updateRequest.ruleName().equals("b"));
  }

  @Test
  void testLimitExceededServiceException() {
    doThrow(ServiceQuotaExceededException.builder().message("too many analyzers for account").build())
        .when(proxy)
        .injectCredentialsAndInvokeV2(any(), any());
    val request = ResourceHandlerRequest.<ResourceModel>builder()
        .desiredResourceState(anOldModel)
        .previousResourceState(aNewModel)
        .build();
    val response = invokeHandleRequest(request);
    assertThat(response).isNotNull();
    assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED);
    assertThat(response.getResourceModel()).isNull();
    assertThat(response.getMessage()).startsWith("too many analyzers for account");
    assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.ServiceLimitExceeded);
  }

  @Test
  void testUnknownServiceException() {
    doThrow(new AmazonServiceException("internal failure"))
        .when(proxy)
        .injectCredentialsAndInvokeV2(any(), any());
    val request = ResourceHandlerRequest.<ResourceModel>builder()
        .desiredResourceState(anOldModel)
        .previousResourceState(aNewModel)
        .build();
    val response = invokeHandleRequest(request);
    assertThat(response).isNotNull();
    assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED);
    assertThat(response.getResourceModel()).isNull();
    assertThat(response.getMessage()).startsWith("internal failure");
    assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.ServiceInternalError);
  }

  private static ResourceModel anOldModel = ResourceModel.builder()
      .analyzerName(ANALYZER_NAME)
      .arn(ANALYZER_ARN)
      .type(TestUtil.ACCOUNT)
      .build();

  private static ResourceModel aNewModel = ResourceModel.builder()
      .analyzerName(ANALYZER_NAME)
      .arn(ANALYZER_ARN)
      .type(TestUtil.ACCOUNT)
      .tags(Collections.singleton(Tag.builder().key("a").value("b").build()))
      .build();

  private ProgressEvent<ResourceModel, CallbackContext> invokeHandleRequest(
      ResourceHandlerRequest<ResourceModel> resourceHandlerRequest) {
    return new UpdateHandler()
        .handleRequest(proxy, resourceHandlerRequest, new CallbackContext(), logger);
  }
}