/*
* Copyright 2010-2019 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.
*/
package software.amazon.cloudformation.metrics;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.time.Instant;
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.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.MetricDatum;
import software.amazon.awssdk.services.cloudwatch.model.PutMetricDataRequest;
import software.amazon.awssdk.services.cloudwatch.model.PutMetricDataResponse;
import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;
import software.amazon.cloudformation.Action;
import software.amazon.cloudformation.injection.CloudWatchProvider;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.Logger;

@ExtendWith(MockitoExtension.class)
public class MetricsPublisherImplTest {

    @Mock
    private Logger loggerProxy;

    @Mock
    private CloudWatchProvider providerCloudWatchProvider;

    @Mock
    private CloudWatchClient providerCloudWatchClient;

    private final String resourceTypeName = "AWS::Test::TestModel";

    @BeforeEach
    public void beforeEach() {
        when(providerCloudWatchProvider.get()).thenReturn(providerCloudWatchClient);
        when(providerCloudWatchClient.putMetricData(any(PutMetricDataRequest.class)))
            .thenReturn(mock(PutMetricDataResponse.class));
    }

    @AfterEach
    public void afterEach() {
        verifyNoMoreInteractions(providerCloudWatchProvider);
        verifyNoMoreInteractions(providerCloudWatchClient);
    }

    @Test
    public void testPublishDurationMetric() {
        final MetricsPublisherImpl providerMetricsPublisher = new MetricsPublisherImpl(providerCloudWatchProvider, loggerProxy,
                                                                                       resourceTypeName);
        providerMetricsPublisher.refreshClient();

        final Instant instant = Instant.parse("2019-06-04T17:50:00Z");
        providerMetricsPublisher.publishDurationMetric(instant, Action.UPDATE, 123456);

        final ArgumentCaptor<PutMetricDataRequest> argument1 = ArgumentCaptor.forClass(PutMetricDataRequest.class);
        verify(providerCloudWatchClient).putMetricData(argument1.capture());

        final PutMetricDataRequest request = argument1.getValue();
        assertThat(request.namespace()).isEqualTo(String.format("%s/%s", "AWS/CloudFormation", "AWS/Test/TestModel"));

        assertThat(request.metricData()).hasSize(1);
        final MetricDatum metricDatum = request.metricData().get(0);
        assertThat(metricDatum.metricName()).isEqualTo("HandlerInvocationDuration");
        assertThat(metricDatum.unit()).isEqualTo(StandardUnit.MILLISECONDS);
        assertThat(metricDatum.value()).isEqualTo(123456);
        assertThat(metricDatum.timestamp()).isEqualTo(Instant.parse("2019-06-04T17:50:00Z"));
        assertThat(metricDatum.dimensions()).containsExactlyInAnyOrder(Dimension.builder().name("Action").value("UPDATE").build(),
            Dimension.builder().name("ResourceType").value(resourceTypeName).build());
    }

    @Test
    public void testPublishExceptionMetric() {
        final MetricsPublisherImpl providerMetricsPublisher = new MetricsPublisherImpl(providerCloudWatchProvider, loggerProxy,
                                                                                       resourceTypeName);
        providerMetricsPublisher.refreshClient();

        final Instant instant = Instant.parse("2019-06-03T17:50:00Z");
        final RuntimeException e = new RuntimeException("some error");
        providerMetricsPublisher.publishExceptionMetric(instant, Action.CREATE, e, HandlerErrorCode.InternalFailure);

        final ArgumentCaptor<PutMetricDataRequest> argument1 = ArgumentCaptor.forClass(PutMetricDataRequest.class);
        verify(providerCloudWatchClient).putMetricData(argument1.capture());

        final PutMetricDataRequest request = argument1.getValue();
        assertThat(request.namespace()).isEqualTo(String.format("%s/%s", "AWS/CloudFormation", "AWS/Test/TestModel"));

        assertThat(request.metricData()).hasSize(1);
        final MetricDatum metricDatum = request.metricData().get(0);
        assertThat(metricDatum.metricName()).isEqualTo("HandlerException");
        assertThat(metricDatum.unit()).isEqualTo(StandardUnit.COUNT);
        assertThat(metricDatum.value()).isEqualTo(1.0);
        assertThat(metricDatum.timestamp()).isEqualTo(Instant.parse("2019-06-03T17:50:00Z"));
        assertThat(metricDatum.dimensions()).containsExactlyInAnyOrder(Dimension.builder().name("Action").value("CREATE").build(),
            Dimension.builder().name("ExceptionType").value("class java.lang.RuntimeException").build(),
            Dimension.builder().name("ResourceType").value(resourceTypeName).build(),
            Dimension.builder().name("HandlerErrorCode").value("InternalFailure").build());
    }

    @Test
    public void testPublishInvocationMetric() {
        final MetricsPublisherImpl providerMetricsPublisher = new MetricsPublisherImpl(providerCloudWatchProvider, loggerProxy,
                                                                                       resourceTypeName);
        providerMetricsPublisher.refreshClient();

        final Instant instant = Instant.parse("2019-06-04T17:50:00Z");
        providerMetricsPublisher.publishInvocationMetric(instant, Action.UPDATE);

        final ArgumentCaptor<PutMetricDataRequest> argument1 = ArgumentCaptor.forClass(PutMetricDataRequest.class);
        verify(providerCloudWatchClient).putMetricData(argument1.capture());

        final PutMetricDataRequest request = argument1.getValue();
        assertThat(request.namespace()).isEqualTo(String.format("%s/%s", "AWS/CloudFormation", "AWS/Test/TestModel"));

        assertThat(request.metricData()).hasSize(1);
        final MetricDatum metricDatum = request.metricData().get(0);
        assertThat(metricDatum.metricName()).isEqualTo("HandlerInvocationCount");
        assertThat(metricDatum.unit()).isEqualTo(StandardUnit.COUNT);
        assertThat(metricDatum.value()).isEqualTo(1.0);
        assertThat(metricDatum.timestamp()).isEqualTo(Instant.parse("2019-06-04T17:50:00Z"));
        assertThat(metricDatum.dimensions()).containsExactlyInAnyOrder(Dimension.builder().name("Action").value("UPDATE").build(),
            Dimension.builder().name("ResourceType").value(resourceTypeName).build());
    }
}