package software.amazon.s3outposts.endpoint;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.awssdk.services.s3outposts.S3OutpostsClient;
import software.amazon.awssdk.services.s3outposts.model.*;
import software.amazon.cloudformation.exceptions.CfnNotStabilizedException;
import software.amazon.cloudformation.proxy.*;

import java.time.Duration;
import java.util.Collections;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class DeleteHandlerTest extends AbstractTestBase {

    private DeleteHandler handler;
    private ResourceHandlerRequest<ResourceModel> request;

    @Mock
    private AmazonWebServicesClientProxy proxy;

    @Mock
    private ProxyClient<S3OutpostsClient> proxyClient;

    @Mock
    S3OutpostsClient sdkClient;

    @BeforeEach
    public void setup() {
        handler = new DeleteHandler();
        proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis());
        sdkClient = mock(S3OutpostsClient.class);
        proxyClient = MOCK_PROXY(proxy, sdkClient);
    }

    @AfterEach
    public void tear_down() {
        verifyNoMoreInteractions(proxyClient.client());
    }

    /**
     * Validation Error - Empty Model
     */
    @Test
    public void handleRequest_EmptyModel() {

        request = ResourceHandlerRequest.<ResourceModel>builder()
                .desiredResourceState(REQ_MODEL_EMPTY)
                .awsAccountId(ACCOUNT_ID)
                .build();

        final ProgressEvent<ResourceModel, CallbackContext> response
                = handler.handleRequest(proxy, request, new CallbackContext(false, NUMBER_OF_STABILIZATION_RETRIES), logger);

        assertThat(response).isNotNull();
        assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED);
        assertThat(response.getCallbackContext()).isEqualToComparingFieldByField(FAILURE_CREATE_CALLBACK_CONTEXT);
        assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
        assertThat(response.getResourceModels()).isNull();
        assertThat(response.getMessage()).isEqualTo("Endpoint ARN is required.");
        assertThat(response.getErrorCode()).isEqualTo(HandlerErrorCode.InvalidRequest);
    }

    /**
     * Happy Path
     */
    @Test
    public void handleRequest_SimpleSuccess() {
        request = ResourceHandlerRequest.<ResourceModel>builder()
                .desiredResourceState(REQ_MODEL_ARN)
                .awsAccountId(ACCOUNT_ID)
                .build();

        final DeleteEndpointResponse deleteEndpointResponse = DeleteEndpointResponse.builder().build();
        when(proxyClient.client().deleteEndpoint(any(DeleteEndpointRequest.class))).thenReturn(deleteEndpointResponse);

        final ListEndpointsResponse listEndpointsResponse =
                ListEndpointsResponse.builder()
                        .endpoints(Collections.singletonList(endpoint2))
                        .nextToken(null)
                        .build();

        when(proxyClient.client().listEndpoints(any(ListEndpointsRequest.class))).thenReturn(listEndpointsResponse);
        final ProgressEvent<ResourceModel, CallbackContext> progress =
                handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);

        assertThat(progress).isNotNull();
        assertThat(progress.getStatus()).isEqualTo(OperationStatus.SUCCESS);
        assertThat(progress.getCallbackContext()).isEqualTo(null);
        assertThat(progress.getCallbackDelaySeconds()).isEqualTo(0);
        assertThat(progress.getResourceModel()).isNull();
        assertThat(progress.getResourceModels()).isNull();
        assertThat(progress.getMessage()).isNull();
        assertThat(progress.getErrorCode()).isNull();

        verify(proxyClient.client()).deleteEndpoint(any(DeleteEndpointRequest.class));
        verify(sdkClient, atLeastOnce()).serviceName();

    }

    /**
     * Happy Path - Re-entry on stabilize function
     */
    @Test
    public void handleRequest_Success_Stabilized() {
        request = ResourceHandlerRequest.<ResourceModel>builder()
                .desiredResourceState(REQ_MODEL_ARN)
                .awsAccountId(ACCOUNT_ID)
                .build();

        final DeleteEndpointResponse deleteEndpointResponse = DeleteEndpointResponse.builder().build();
        when(proxyClient.client().deleteEndpoint(any(DeleteEndpointRequest.class))).thenReturn(deleteEndpointResponse);

        final ListEndpointsResponse listEndpointsResponse =
                ListEndpointsResponse.builder()
                        .endpoints(Collections.singletonList(endpoint2))
                        .nextToken(null)
                        .build();

        when(proxyClient.client().listEndpoints(any(ListEndpointsRequest.class))).thenReturn(listEndpointsResponse);

        CallbackContext context = new CallbackContext();
        context.setStabilized(true);

        final ProgressEvent<ResourceModel, CallbackContext> progress =
                handler.handleRequest(proxy, request, context, proxyClient, logger);

        assertThat(progress).isNotNull();
        assertThat(progress.getStatus()).isEqualTo(OperationStatus.SUCCESS);
        assertThat(progress.getCallbackContext()).isEqualTo(null);
        assertThat(progress.getCallbackDelaySeconds()).isEqualTo(0);
        assertThat(progress.getResourceModel()).isNull();
        assertThat(progress.getResourceModels()).isNull();
        assertThat(progress.getMessage()).isNull();
        assertThat(progress.getErrorCode()).isNull();

        verify(proxyClient.client()).deleteEndpoint(any(DeleteEndpointRequest.class));
        verify(sdkClient, atLeastOnce()).serviceName();

    }

    /**
     * Max Retries reached waiting for Resource to stabilize
     */
    @Test
    public void handleRequest_MaxRetryAttempts_Success() {

        request = ResourceHandlerRequest.<ResourceModel>builder()
                .desiredResourceState(REQ_MODEL_ARN)
                .awsAccountId(ACCOUNT_ID)
                .build();

        final DeleteEndpointResponse deleteEndpointResponse = DeleteEndpointResponse.builder().build();
        when(proxyClient.client().deleteEndpoint(any(DeleteEndpointRequest.class))).thenReturn(deleteEndpointResponse);

        final ListEndpointsResponse listEndpointsResponse =
                ListEndpointsResponse.builder()
                        .endpoints(Collections.singletonList(endpoint5))
                        .nextToken(null)
                        .build();

        when(proxyClient.client().listEndpoints(any(ListEndpointsRequest.class))).thenReturn(listEndpointsResponse);
        final ProgressEvent<ResourceModel, CallbackContext> progress =
                handler.handleRequest(proxy, request, new CallbackContext(false, NUMBER_OF_STABILIZATION_RETRIES), proxyClient, logger);

        assertThat(progress).isNotNull();
        assertThat(progress.getStatus()).isEqualTo(OperationStatus.SUCCESS);
        assertThat(progress.getCallbackContext()).isEqualTo(null);
        assertThat(progress.getCallbackDelaySeconds()).isEqualTo(0);
        assertThat(progress.getResourceModel()).isNull();
        assertThat(progress.getResourceModels()).isNull();
        assertThat(progress.getMessage()).isNull();
        assertThat(progress.getErrorCode()).isNull();

        verify(proxyClient.client()).deleteEndpoint(any(DeleteEndpointRequest.class));
        verify(proxyClient.client(), times(NUMBER_OF_STABILIZATION_RETRIES)).listEndpoints(any(ListEndpointsRequest.class));
        verify(sdkClient, atLeastOnce()).serviceName();

    }

    /**
     * CFNStabilizationException when Read Handler fails for an error other than 404 Not Found.
     */
    @Test
    public void handleRequest_InvalidResponseStatus_StabilizedException() {

        request = ResourceHandlerRequest.<ResourceModel>builder()
                .desiredResourceState(REQ_MODEL_ARN)
                .awsAccountId(ACCOUNT_ID)
                .build();

        final DeleteEndpointResponse deleteEndpointResponse = DeleteEndpointResponse.builder().build();
        when(proxyClient.client().deleteEndpoint(any(DeleteEndpointRequest.class))).thenReturn(deleteEndpointResponse);

        when(proxyClient.client().listEndpoints(any(ListEndpointsRequest.class))).thenThrow(S3OutpostsException.builder().statusCode(403).build());

        Exception exception = assertThrows(
                CfnNotStabilizedException.class,
                () -> handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger)
        );
        assertThat(exception.getMessage()).contains("did not stabilize.");
        verify(proxyClient.client()).deleteEndpoint(any(DeleteEndpointRequest.class));
        verify(sdkClient, atLeastOnce()).serviceName();

    }

    /**
     * Error Path - ResourceConflict
     */
    @Test
    public void handleRequest_Error_409() {

        request = ResourceHandlerRequest.<ResourceModel>builder()
                .desiredResourceState(REQ_MODEL_ARN)
                .awsAccountId(ACCOUNT_ID)
                .build();

        when(proxyClient.client().deleteEndpoint(any(DeleteEndpointRequest.class)))
                .thenThrow(constructS3OutpostsExceptionWithStatusCode(409));

        final ProgressEvent<ResourceModel, CallbackContext> progress =
                handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);

        assertThat(progress).isNotNull();
        assertThat(progress.getStatus()).isEqualTo(OperationStatus.FAILED);
        assertThat(progress.getCallbackContext()).isEqualToComparingOnlyGivenFields(new CallbackContext());
        assertThat(progress.getCallbackDelaySeconds()).isEqualTo(0);
        assertThat(progress.getResourceModels()).isNull();
        assertThat(progress.getMessage()).isNull();
        assertThat(progress.getErrorCode()).isEqualTo(HandlerErrorCode.ResourceConflict);

        verify(proxyClient.client()).deleteEndpoint(any(DeleteEndpointRequest.class));
        verify(sdkClient, atLeastOnce()).serviceName();

    }
}