package de.taimos.pipeline.aws;

import java.util.Collections;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import com.amazonaws.client.builder.AwsSyncClientBuilder;
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
import com.amazonaws.services.cloudformation.AmazonCloudFormationClientBuilder;
import com.amazonaws.services.cloudformation.model.DescribeStackResourcesRequest;
import com.amazonaws.services.cloudformation.model.DescribeStackResourcesResult;
import com.amazonaws.services.cloudformation.model.StackResource;
import com.amazonaws.services.lambda.model.AliasConfiguration;
import com.amazonaws.services.lambda.model.ListVersionsByFunctionRequest;
import com.amazonaws.services.lambda.model.ListVersionsByFunctionResult;
import com.amazonaws.services.lambda.model.DeleteFunctionRequest;
import com.amazonaws.services.lambda.model.FunctionConfiguration;
import com.amazonaws.services.lambda.model.ListAliasesRequest;
import com.amazonaws.services.lambda.model.ListAliasesResult;
import com.amazonaws.services.lambda.AWSLambdaClientBuilder;

import de.taimos.pipeline.aws.AWSClientFactory;
import hudson.EnvVars;
import hudson.model.Run;
import hudson.model.TaskListener;
import com.amazonaws.services.lambda.AWSLambda;

import java.util.Arrays;

	@RunWith(PowerMockRunner.class)
	@PrepareForTest(AWSClientFactory.class)
	@PowerMockIgnore("javax.crypto.*")
	public class LambdaVersionCleanupStepTest {

		@Rule
		private JenkinsRule jenkinsRule = new JenkinsRule();
		private AWSLambda awsLambda;
		private AmazonCloudFormation cloudformation;

		@Before
		public void setupSdk() throws Exception {
			PowerMockito.mockStatic(AWSClientFactory.class);
			this.awsLambda = Mockito.mock(AWSLambda.class);
			this.cloudformation = Mockito.mock(AmazonCloudFormation.class);
			PowerMockito.when(AWSClientFactory.create(Mockito.any(AwsSyncClientBuilder.class), Mockito.any(StepContext.class)))
				.thenAnswer( (x) -> {
					if (x.getArgumentAt(0, AwsSyncClientBuilder.class) instanceof AWSLambdaClientBuilder) {
						return awsLambda;
					} else {
						return cloudformation;
					}
				});
		}

		@Test
		public void deleteSingleFunction() throws Exception {
			WorkflowJob job = this.jenkinsRule.jenkins.createProject(WorkflowJob.class, "cfnTest");
			Mockito.when(this.awsLambda.listAliases(Mockito.eq(new ListAliasesRequest().withFunctionName("foo")))).thenReturn(new ListAliasesResult());
			Mockito.when(this.awsLambda.listVersionsByFunction(Mockito.eq(new ListVersionsByFunctionRequest().withFunctionName("foo")))).thenReturn(new ListVersionsByFunctionResult()
					.withVersions(Arrays.asList(
							new FunctionConfiguration().withVersion("v1").withLastModified(ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)),
							new FunctionConfiguration().withVersion("v2").withLastModified("2018-02-05T11:15:12Z")
							))
					);
			job.setDefinition(new CpsFlowDefinition(""
						+ "node {\n"
						+ "  lambdaVersionCleanup(functionName: 'foo', daysAgo: 5)\n"
						+ "}\n", true)
					);
			this.jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0));

			Mockito.verify(this.awsLambda).deleteFunction(new DeleteFunctionRequest()
					.withQualifier("v2")
					.withFunctionName("foo")
					);
			Mockito.verify(this.awsLambda).listVersionsByFunction(Mockito.any());
			Mockito.verify(this.awsLambda).listAliases(Mockito.any());
			Mockito.verifyNoMoreInteractions(this.awsLambda);
		}

		@Test
		public void paginatedResponse() throws Exception {
			WorkflowJob job = this.jenkinsRule.jenkins.createProject(WorkflowJob.class, "cfnTest");
			Mockito.when(this.awsLambda.listAliases(Mockito.eq(new ListAliasesRequest().withFunctionName("foo")))).thenReturn(new ListAliasesResult());
			Mockito.when(this.awsLambda.listVersionsByFunction(Mockito.eq(new ListVersionsByFunctionRequest().withFunctionName("foo")))).thenReturn(new ListVersionsByFunctionResult()
					.withNextMarker("baz")
					);
			Mockito.when(this.awsLambda.listVersionsByFunction(Mockito.eq(new ListVersionsByFunctionRequest().withFunctionName("foo").withMarker("baz")))).thenReturn(new ListVersionsByFunctionResult()
					.withVersions(Arrays.asList(
							new FunctionConfiguration().withVersion("v2").withLastModified("2018-02-05T11:15:12Z")
							))
					);
			job.setDefinition(new CpsFlowDefinition(""
						+ "node {\n"
						+ "  lambdaVersionCleanup(functionName: 'foo', daysAgo: 5)\n"
						+ "}\n", true)
					);
			this.jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0));

			Mockito.verify(this.awsLambda).deleteFunction(new DeleteFunctionRequest()
					.withQualifier("v2")
					.withFunctionName("foo")
					);
			Mockito.verify(this.awsLambda, Mockito.times(2)).listVersionsByFunction(Mockito.any());
			Mockito.verify(this.awsLambda).listAliases(Mockito.any());
			Mockito.verifyNoMoreInteractions(this.awsLambda);
		}

		@Test
		public void ignoreLatest() throws Exception {
			WorkflowJob job = this.jenkinsRule.jenkins.createProject(WorkflowJob.class, "cfnTest");
			Mockito.when(this.awsLambda.listAliases(Mockito.eq(new ListAliasesRequest().withFunctionName("foo")))).thenReturn(new ListAliasesResult());
			Mockito.when(this.awsLambda.listVersionsByFunction(Mockito.eq(new ListVersionsByFunctionRequest().withFunctionName("foo")))).thenReturn(new ListVersionsByFunctionResult()
					.withVersions(Arrays.asList(
							new FunctionConfiguration().withVersion("$LATEST").withLastModified(ZonedDateTime.now().minusDays(15).format(DateTimeFormatter.ISO_ZONED_DATE_TIME))
							))
					);
			job.setDefinition(new CpsFlowDefinition(""
						+ "node {\n"
						+ "  lambdaVersionCleanup(functionName: 'foo', daysAgo: 5)\n"
						+ "}\n", true)
					);
			this.jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0));

			Mockito.verify(this.awsLambda).listVersionsByFunction(Mockito.any());
			Mockito.verify(this.awsLambda).listAliases(Mockito.any());
			Mockito.verifyNoMoreInteractions(this.awsLambda);
		}

		@Test
		public void ignoreAliases() throws Exception {
			WorkflowJob job = this.jenkinsRule.jenkins.createProject(WorkflowJob.class, "cfnTest");
			Mockito.when(this.awsLambda.listAliases(Mockito.eq(new ListAliasesRequest().withFunctionName("foo")))).thenReturn(new ListAliasesResult()
					.withAliases(
						new AliasConfiguration().withFunctionVersion("myVersion")
					)
					);
			Mockito.when(this.awsLambda.listVersionsByFunction(Mockito.eq(new ListVersionsByFunctionRequest().withFunctionName("foo")))).thenReturn(new ListVersionsByFunctionResult()
					.withVersions(Arrays.asList(
							new FunctionConfiguration().withVersion("myVersion").withLastModified(ZonedDateTime.now().minusDays(15).format(DateTimeFormatter.ISO_ZONED_DATE_TIME))
							))
					);
			job.setDefinition(new CpsFlowDefinition(""
						+ "node {\n"
						+ "  lambdaVersionCleanup(functionName: 'foo', daysAgo: 5)\n"
						+ "}\n", true)
					);
			this.jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0));

			Mockito.verify(this.awsLambda).listVersionsByFunction(Mockito.any());
			Mockito.verify(this.awsLambda).listAliases(Mockito.any());
			Mockito.verifyNoMoreInteractions(this.awsLambda);
		}

		@Test
		public void deleteCloudFormationStack() throws Exception {
			WorkflowJob job = this.jenkinsRule.jenkins.createProject(WorkflowJob.class, "cfnTest");
			Mockito.when(this.awsLambda.listAliases(Mockito.eq(new ListAliasesRequest().withFunctionName("foo")))).thenReturn(new ListAliasesResult());
			Mockito.when(this.awsLambda.listVersionsByFunction(Mockito.eq(new ListVersionsByFunctionRequest().withFunctionName("foo")))).thenReturn(new ListVersionsByFunctionResult()
					.withVersions(Arrays.asList(
							new FunctionConfiguration().withVersion("v1").withLastModified(ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)),
							new FunctionConfiguration().withVersion("v2").withLastModified("2018-02-05T11:15:12Z")
							))
					);
			Mockito.when(this.cloudformation.describeStackResources(new DescribeStackResourcesRequest().withStackName("baz"))).thenReturn(new DescribeStackResourcesResult().withStackResources(
						new StackResource()
						.withResourceType("AWS::Lambda::Function")
						.withPhysicalResourceId("foo"),
						new StackResource()
						.withResourceType("AWS::Baz::Function")
						.withPhysicalResourceId("bar")
						)
					);
			job.setDefinition(new CpsFlowDefinition(""
						+ "node {\n"
						+ "  lambdaVersionCleanup(stackName: 'baz', daysAgo: 5)\n"
						+ "}\n", true)
					);
			this.jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0));

			Mockito.verify(this.awsLambda).deleteFunction(new DeleteFunctionRequest()
					.withQualifier("v2")
					.withFunctionName("foo")
					);
			Mockito.verify(this.awsLambda).listVersionsByFunction(Mockito.any());
			Mockito.verify(this.awsLambda).listAliases(Mockito.any());
			Mockito.verifyNoMoreInteractions(this.awsLambda);
		}


	}