/*
 * Copyright 2016-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License 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 org.springframework.cloud.deployer.spi.cloudfoundry;

import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.Metadata;
import org.cloudfoundry.client.v2.applications.ApplicationsV2;
import org.cloudfoundry.client.v2.applications.SummaryApplicationResponse;
import org.cloudfoundry.client.v2.organizations.ListOrganizationsResponse;
import org.cloudfoundry.client.v2.organizations.OrganizationResource;
import org.cloudfoundry.client.v2.organizations.Organizations;
import org.cloudfoundry.client.v2.spaces.ListSpacesResponse;
import org.cloudfoundry.client.v2.spaces.SpaceResource;
import org.cloudfoundry.client.v2.spaces.Spaces;
import org.cloudfoundry.client.v3.Pagination;
import org.cloudfoundry.client.v3.tasks.CancelTaskRequest;
import org.cloudfoundry.client.v3.tasks.CancelTaskResponse;
import org.cloudfoundry.client.v3.tasks.CreateTaskRequest;
import org.cloudfoundry.client.v3.tasks.CreateTaskResponse;
import org.cloudfoundry.client.v3.tasks.GetTaskRequest;
import org.cloudfoundry.client.v3.tasks.GetTaskResponse;
import org.cloudfoundry.client.v3.tasks.ListTasksResponse;
import org.cloudfoundry.client.v3.tasks.TaskResource;
import org.cloudfoundry.client.v3.tasks.TaskState;
import org.cloudfoundry.client.v3.tasks.Tasks;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.ApplicationSummary;
import org.cloudfoundry.operations.applications.Applications;
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
import org.cloudfoundry.operations.applications.GetApplicationRequest;
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
import org.cloudfoundry.operations.applications.StopApplicationRequest;
import org.cloudfoundry.operations.services.Services;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.cloud.deployer.spi.core.AppDefinition;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.cloud.deployer.spi.core.RuntimeEnvironmentInfo;
import org.springframework.cloud.deployer.spi.task.LaunchState;
import org.springframework.cloud.deployer.spi.task.TaskStatus;
import org.springframework.cloud.deployer.spi.util.ByteSizeUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;

/**
 * @author Michael Minella
 * @author Ben Hale
 * @author Glenn Renfro
 * @author David Turanski
 */
public class CloudFoundryTaskLauncherTests {
	private final static int TASK_EXECUTION_COUNT = 10;

	private final CloudFoundryDeploymentProperties deploymentProperties = new CloudFoundryDeploymentProperties();

	@Mock(answer = Answers.RETURNS_SMART_NULLS)
	private Applications applications;

	@Mock(answer = Answers.RETURNS_SMART_NULLS)
	private ApplicationsV2 applicationsV2;

	@Mock(answer = Answers.RETURNS_SMART_NULLS)
	private CloudFoundryClient client;

	private CloudFoundryTaskLauncher launcher;

	@Mock(answer = Answers.RETURNS_SMART_NULLS)
	private CloudFoundryOperations operations;

	@Mock(answer = Answers.RETURNS_SMART_NULLS)
	private Services services;

	@Mock(answer = Answers.RETURNS_SMART_NULLS)
	private Spaces spaces;

	@Mock(answer = Answers.RETURNS_SMART_NULLS)
	private Organizations organizations;

	@Mock(answer = Answers.RETURNS_SMART_NULLS)
	private Tasks tasks;

	private Resource resource = new FileSystemResource("src/test/resources/demo-0.0.1-SNAPSHOT.jar");;

	@Before
	public void setUp() {
		MockitoAnnotations.initMocks(this);
		given(this.tasks.list(any())).willReturn(this.runningTasksResponse());
		given(this.client.applicationsV2()).willReturn(this.applicationsV2);
		given(this.client.tasks()).willReturn(this.tasks);

		given(this.operations.applications()).willReturn(this.applications);
		given(this.operations.services()).willReturn(this.services);
		given(this.client.spaces()).willReturn(this.spaces);
		given(this.client.organizations()).willReturn(this.organizations);

		RuntimeEnvironmentInfo runtimeEnvironmentInfo = mock(RuntimeEnvironmentInfo.class);
		Map<String, String> orgAndSpace = new HashMap<>();
		orgAndSpace.put(CloudFoundryPlatformSpecificInfo.ORG, "this-org");
		orgAndSpace.put(CloudFoundryPlatformSpecificInfo.SPACE, "this-space");
		given(runtimeEnvironmentInfo.getPlatformSpecificInfo()).willReturn(orgAndSpace);
		given(this.organizations.list(any())).willReturn(listOrganizationsResponse());
		given(this.spaces.list(any())).willReturn(listSpacesResponse());

		this.deploymentProperties.setApiTimeout(1);
		this.deploymentProperties.setStatusTimeout(1_250L);
		this.launcher = new CloudFoundryTaskLauncher(this.client,
				this.deploymentProperties,
				this.operations,
				runtimeEnvironmentInfo);

	}

	@Test
	public void cancel() {
		givenRequestCancelTask("test-task-id", Mono.just(CancelTaskResponse.builder()
			.id("test-task-id")
			.memoryInMb(1024)
			.diskInMb(1024)
			.dropletId("1")
			.createdAt(new Date().toString())
			.updatedAt(new Date().toString())
			.sequenceId(1)
			.name("test-task-id")
			.state(TaskState.CANCELING)
			.build()));

		this.launcher.cancel("test-task-id");
	}

	@Test
	public void currentExecutionCount() {
		assertThat(this.launcher.getRunningTaskExecutionCount(), equalTo(this.TASK_EXECUTION_COUNT));
	}

	@Test
	public void launchTaskApplicationExists() {
		setupExistingAppSuccessful();
		String taskId = this.launcher.launch(defaultRequest());

		assertThat(taskId, equalTo("test-task-id"));
	}

	@Test
	public void stageTaskApplicationExists() {
		setupExistingAppSuccessful();
		SummaryApplicationResponse response = this.launcher.stage(defaultRequest());

		assertThat(response.getId(), equalTo("test-application-id"));
		assertThat(response.getDetectedStartCommand(), equalTo("test-command"));
	}

	@Test
	public void launchTaskWithNonExistentApplication() throws IOException {
		setupTaskWithNonExistentApplication(this.resource);
		String taskId = this.launcher.launch(defaultRequest());

		assertThat(taskId, equalTo("test-task-id"));
	}

	@Test
	public void launchExistingTaskApplicationWithPushDisabled() {
		setupExistingAppSuccessful();
		deploymentProperties.setPushTaskAppsEnabled(false);
		String taskId = this.launcher.launch(defaultRequest());
		assertThat(taskId, equalTo("test-task-id"));
	}

	@Test(expected = IllegalStateException.class)
	public void launchNonExistingTaskApplicationWithPushDisabled() throws IOException {
		setupTaskWithNonExistentApplication(this.resource);
		deploymentProperties.setPushTaskAppsEnabled(false);
		String taskId = this.launcher.launch(defaultRequest());
		assertThat(taskId, equalTo("test-task-id"));
	}

	@Test
	public void stageTaskWithNonExistentApplication() throws IOException {
		setupTaskWithNonExistentApplication(this.resource);

		SummaryApplicationResponse response = this.launcher.stage(defaultRequest());
		assertThat(response.getId(), equalTo("test-application-id"));
		assertThat(response.getDetectedStartCommand(), equalTo("test-command"));
	}

	@Test
	public void automaticallyConfigureForCfEnv() throws JsonProcessingException {
		Resource resource = new FileSystemResource("src/test/resources/log-sink-rabbit-3.0.0.BUILD-SNAPSHOT.jar");
		AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(new AppDefinition("test-application",
					Collections.EMPTY_MAP), resource, Collections.EMPTY_MAP);

		Map<String, String> env =  launcher.getEnvironmentVariables("test-application-id", appDeploymentRequest);
		MatcherAssert.assertThat(env, hasEntry(CfEnvConfigurer.JBP_CONFIG_SPRING_AUTO_RECONFIGURATION, CfEnvConfigurer.ENABLED_FALSE));
		MatcherAssert.assertThat(env, hasKey(CoreMatchers.equalTo("SPRING_APPLICATION_JSON")));
		ObjectMapper objectMapper = new ObjectMapper();
		Map<String, String> saj = objectMapper.readValue(env.get("SPRING_APPLICATION_JSON"),HashMap.class);
		MatcherAssert.assertThat(saj, hasEntry(CfEnvConfigurer.SPRING_PROFILES_ACTIVE_FQN, CfEnvConfigurer.CLOUD_PROFILE_NAME));
	}


	@Test(expected = UnsupportedOperationException.class)
	public void launchTaskWithNonExistentApplicationAndApplicationListingFails() {
		givenRequestListApplications(Flux.error(new UnsupportedOperationException()));

		this.launcher.launch(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void stageTaskWithNonExistentApplicationAndApplicationListingFails() {
		givenRequestListApplications(Flux.error(new UnsupportedOperationException()));

		this.launcher.stage(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void launchTaskWithNonExistentApplicationAndPushFails() throws IOException {
		setupFailedPush(this.resource);

		this.launcher.launch(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void stageTaskWithNonExistentApplicationAndPushFails() throws IOException {
		setupFailedPush(this.resource);

		this.launcher.stage(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void launchTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails() throws IOException {
		setupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(this.resource);

		this.launcher.launch(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void stageTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails() throws IOException {
		setupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(this.resource);

		this.launcher.stage(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void launchTaskWithNonExistentApplicationAndStoppingApplicationFails() throws IOException {
		setupTaskWithNonExistentApplicationAndStoppingApplicationFails(this.resource);

		this.launcher.launch(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void stageTaskWithNonExistentApplicationAndStoppingApplicationFails() throws IOException {
		setupTaskWithNonExistentApplicationAndStoppingApplicationFails(this.resource);

		this.launcher.stage(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void launchTaskWithNonExistentApplicationAndTaskCreationFails() throws IOException {
		givenRequestListApplications(Flux.empty());

		givenRequestPushApplication(PushApplicationManifestRequest.builder()
			.manifest(ApplicationManifest.builder()
				.path(this.resource.getFile().toPath())
				.buildpack(deploymentProperties.getBuildpack())
				.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
				.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
				.environmentVariable("SPRING_APPLICATION_JSON", "{}")
				.healthCheckType(ApplicationHealthCheck.NONE)
				.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
				.name("test-application")
				.noRoute(true)
				.services(Collections.emptySet())
				.build())
			.stagingTimeout(this.deploymentProperties.getStagingTimeout())
			.startupTimeout(this.deploymentProperties.getStartupTimeout())
			.build(), Mono.empty());

		givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
			.diskQuota(0)
			.id("test-application-id")
			.instances(1)
			.memoryLimit(0)
			.name("test-application")
			.requestedState("RUNNING")
			.runningInstances(1)
			.stack("test-stack")
			.build()));

		givenRequestStopApplication("test-application", Mono.empty());

		givenRequestGetApplicationSummary("test-application-id",
				Mono.just(SummaryApplicationResponse.builder()
			.id("test-application-id")
			.detectedStartCommand("test-command")
			.build()));

		givenRequestCreateTask("test-application-id",
				"test-command",
				this.deploymentProperties.getMemory(),
				"test-application",
				Mono.error(new UnsupportedOperationException()));

		this.launcher.launch(defaultRequest());
	}

	@Test
	public void launchTaskWithNonExistentApplicationBindingOneService() throws IOException {
		setupTaskWithNonExistentApplicationBindingOneService(this.resource);
		AppDeploymentRequest request = deploymentRequest(this.resource,
			Collections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, "test-service-instance-2"));

		String taskId = this.launcher.launch(request);
		assertThat(taskId, equalTo("test-task-id"));
	}

	@Test
	public void stageTaskWithNonExistentApplicationBindingOneService() throws IOException {
		setupTaskWithNonExistentApplicationBindingOneService(this.resource);
		AppDeploymentRequest request = deploymentRequest(this.resource,
				Collections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY, "test-service-instance-2"));

		SummaryApplicationResponse response = this.launcher.stage(request);
		assertThat(response.getId(), equalTo("test-application-id"));
		assertThat(response.getDetectedStartCommand(), equalTo("test-command"));
	}

	@Test
	public void launchTaskWithNonExistentApplicationBindingThreeServices() throws IOException {
		setupTaskWithNonExistentApplicationBindingThreeServices(this.resource);
		AppDeploymentRequest request = deploymentRequest(this.resource,
			Collections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY,
					"test-service-instance-1,test-service-instance-2,test-service-instance-3"));

		String taskId = this.launcher.launch(request);

		assertThat(taskId, equalTo("test-task-id"));
	}
	@Test
	public void stageTaskWithNonExistentApplicationBindingThreeServices() throws IOException {
		setupTaskWithNonExistentApplicationBindingThreeServices(this.resource);
		AppDeploymentRequest request = deploymentRequest(this.resource,
				Collections.singletonMap(CloudFoundryDeploymentProperties.SERVICES_PROPERTY_KEY,
						"test-service-instance-1,test-service-instance-2,test-service-instance-3"));

		SummaryApplicationResponse response = this.launcher.stage(request);
		assertThat(response.getId(), equalTo("test-application-id"));
		assertThat(response.getDetectedStartCommand(), equalTo("test-command"));
	}

	@Test(expected = UnsupportedOperationException.class)
	public void launchTaskWithNonExistentApplicationRetrievalFails() throws IOException {
		setupTaskWithNonExistentApplicationRetrievalFails(this.resource);

		this.launcher.launch(defaultRequest());
	}

	@Test(expected = UnsupportedOperationException.class)
	public void stageTaskWithNonExistentApplicationRetrievalFails() throws IOException {
		setupTaskWithNonExistentApplicationRetrievalFails(this.resource);

		this.launcher.stage(defaultRequest());
	}

	@Test
	public void status() {
		givenRequestGetTask("test-task-id", Mono.just(GetTaskResponse.builder()
			.id("test-task-id")
				.memoryInMb(1024)
				.diskInMb(1024)
				.dropletId("1")
				.createdAt(new Date().toString())
				.updatedAt(new Date().toString())
				.sequenceId(1)
				.name("test-task-id")
			.state(TaskState.SUCCEEDED)
			.build()));

		TaskStatus status = this.launcher.status("test-task-id");

		assertThat(status.getState(), equalTo(LaunchState.complete));
	}

	@Test
	public void testStatusTimeout() {
		// Delay twice as much as 40% of statusTimeout, which is what the deployer uses
		long delay = (long) (this.deploymentProperties.getStatusTimeout() * .4f * 2);

		givenRequestGetTask("test-task-id", Mono
			.delay(Duration.ofMillis(delay))
			.then(Mono.just(GetTaskResponse.builder()
				.id("test-task-id")
					.memoryInMb(1024)
					.diskInMb(1024)
					.dropletId("1")
					.createdAt(new Date().toString())
					.updatedAt(new Date().toString())
					.sequenceId(1)
					.name("test-task-id")
				.state(TaskState.SUCCEEDED)
				.build())));

		assertThat(this.launcher.status("test-task-id").getState(), equalTo(LaunchState.error));
	}

	@Test
	public void testDestroy() {
		givenRequestDeleteApplication("test-application");

		this.launcher.destroy("test-application");
	}

	@Test
	public void testCommand() {
		AppDeploymentRequest request = new AppDeploymentRequest(new AppDefinition(
				"test-app-1", null),
				this.resource,
				Collections.singletonMap("test-key-1", "test-val-1"),
				Collections.singletonList("test-command-arg-1"));
		String command = this.launcher.getCommand(SummaryApplicationResponse
				.builder()
				.detectedStartCommand("command-val")
				.build(),
				request);
		assertThat(command, equalTo("command-val test-command-arg-1"));
	}

	private void givenRequestCancelTask(String taskId, Mono<CancelTaskResponse> response) {
		given(this.client.tasks()
			.cancel(CancelTaskRequest.builder()
				.taskId(taskId)
				.build()))
			.willReturn(response);
	}

	private void givenRequestCreateTask(String applicationId,
			String command,
			String memory,
			String name,
			Mono<CreateTaskResponse> response) {

		given(this.client.tasks()
			.create(CreateTaskRequest.builder()
				.applicationId(applicationId)
				.command(command)
				.memoryInMb((int) ByteSizeUtils.parseToMebibytes(memory))
				.name(name)
				.build()))
			.willReturn(response);
	}

	private void givenRequestDeleteApplication(String appName) {
		given(this.operations.applications()
			.delete(DeleteApplicationRequest.builder()
				.name(appName)
				.deleteRoutes(true)
				.build()))
			.willReturn(Mono.empty());
	}

	private void givenRequestGetApplication(String name, Mono<ApplicationDetail> response) {
		given(this.operations.applications()
			.get(GetApplicationRequest.builder()
				.name(name)
				.build()))
			.willReturn(response);
	}

	private void givenRequestGetApplicationSummary(String applicationId, Mono<SummaryApplicationResponse> response) {
		given(this.client.applicationsV2()
			.summary(org.cloudfoundry.client.v2.applications.SummaryApplicationRequest.builder()
				.applicationId(applicationId)
				.build()))
			.willReturn(response);
	}

	private void givenRequestGetTask(String taskId, Mono<GetTaskResponse> response) {
		given(this.client.tasks()
			.get(GetTaskRequest.builder()
				.taskId(taskId)
				.build()))
			.willReturn(response);
	}

	private void givenRequestListApplications(Flux<ApplicationSummary> response) {
		given(this.operations.applications()
			.list())
			.willReturn(response);
	}

	private void givenRequestPushApplication(PushApplicationManifestRequest request, Mono<Void> response) {
		given(this.operations.applications()
			.pushManifest(any(PushApplicationManifestRequest.class)))
			.willReturn(response);
	}

	private void givenRequestStopApplication(String name, Mono<Void> response) {
		given(this.operations.applications()
			.stop(StopApplicationRequest.builder()
				.name(name)
				.build()))
			.willReturn(response);
	}

	private void setupExistingAppSuccessful() {
			givenRequestListApplications(Flux.just(ApplicationSummary.builder()
					.diskQuota(0)
					.id("test-application-id")
					.instances(1)
					.memoryLimit(0)
					.name("test-application")
					.requestedState("RUNNING")
					.runningInstances(1)
					.build()));

			givenRequestGetApplicationSummary("test-application-id", Mono.just(SummaryApplicationResponse.builder()
					.id("test-application-id")
					.detectedStartCommand("test-command")
					.build()));

			givenRequestCreateTask("test-application-id",
						"test-command",
						this.deploymentProperties.getMemory(),
						"test-application",
						Mono.just(CreateTaskResponse.builder()
					.id("test-task-id")
					.memoryInMb(1024)
					.diskInMb(1024)
					.dropletId("1")
					.createdAt(new Date().toString())
					.updatedAt(new Date().toString())
					.sequenceId(1)
					.name("test-task-id")
					.state(TaskState.FAILED)
					.build()));
	}

	private void setupTaskWithNonExistentApplication(Resource resource) throws IOException{
		givenRequestListApplications(Flux.empty());

		givenRequestPushApplication(
				PushApplicationManifestRequest.builder()
						.manifest(ApplicationManifest.builder()
								.path(resource.getFile().toPath())
								.buildpack(deploymentProperties.getBuildpack())
								.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
								.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
								.environmentVariable("SPRING_APPLICATION_JSON", "{}")
								.healthCheckType(ApplicationHealthCheck.NONE)
								.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
								.name("test-application")
								.noRoute(true)
								.services(Collections.emptySet())
								.build())
						.stagingTimeout(this.deploymentProperties.getStagingTimeout())
						.startupTimeout(this.deploymentProperties.getStartupTimeout())
						.build(), Mono.empty());

		givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
				.diskQuota(0)
				.id("test-application-id")
				.instances(1)
				.memoryLimit(0)
				.name("test-application")
				.requestedState("RUNNING")
				.runningInstances(1)
				.stack("test-stack")
				.build()));

		givenRequestStopApplication("test-application", Mono.empty());

		givenRequestGetApplicationSummary("test-application-id", Mono.just(SummaryApplicationResponse.builder()
				.id("test-application-id")
				.detectedStartCommand("test-command")
				.build()));

		givenRequestCreateTask("test-application-id",
					"test-command",
					this.deploymentProperties.getMemory(),
					"test-application",
					Mono.just(CreateTaskResponse.builder()
				.id("test-task-id")
				.memoryInMb(1024)
				.diskInMb(1024)
				.dropletId("1")
				.createdAt(new Date().toString())
				.updatedAt(new Date().toString())
				.sequenceId(1)
				.name("test-task-id")
				.state(TaskState.SUCCEEDED)
				.build()));
	}

	private void setupFailedPush(Resource resource) throws IOException{
		givenRequestListApplications(Flux.empty());

		givenRequestPushApplication(
				PushApplicationManifestRequest.builder()
						.manifest(ApplicationManifest.builder()
								.path(resource.getFile().toPath())
								.buildpack(deploymentProperties.getBuildpack())
								.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
								.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
								.environmentVariable("SPRING_APPLICATION_JSON", "{}")
								.healthCheckType(ApplicationHealthCheck.NONE)
								.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
								.name("test-application")
								.noRoute(true)
								.services(Collections.emptySet())
								.build())
						.stagingTimeout(this.deploymentProperties.getStagingTimeout())
						.startupTimeout(this.deploymentProperties.getStartupTimeout())
						.build(), Mono.error(new UnsupportedOperationException()));
	}

	private void setupTaskWithNonExistentApplicationAndRetrievingApplicationSummaryFails(Resource resource) throws IOException {
		givenRequestListApplications(Flux.empty());

		givenRequestPushApplication(
				PushApplicationManifestRequest.builder()
						.manifest(ApplicationManifest.builder()
								.path(resource.getFile().toPath())
								.buildpack(deploymentProperties.getBuildpack())
								.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
								.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
								.environmentVariable("SPRING_APPLICATION_JSON", "{}")
								.healthCheckType(ApplicationHealthCheck.NONE)
								.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
								.name("test-application")
								.noRoute(true)
								.services(Collections.emptySet())
								.build())
						.stagingTimeout(this.deploymentProperties.getStagingTimeout())
						.startupTimeout(this.deploymentProperties.getStartupTimeout())
						.build(), Mono.empty());

		givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
				.diskQuota(0)
				.id("test-application-id")
				.instances(1)
				.memoryLimit(0)
				.name("test-application")
				.requestedState("RUNNING")
				.runningInstances(1)
				.stack("test-stack")
				.build()));

		givenRequestStopApplication("test-application", Mono.empty());

		givenRequestGetApplicationSummary("test-application-id", Mono.error(new UnsupportedOperationException()));

	}

	private void setupTaskWithNonExistentApplicationAndStoppingApplicationFails(Resource resource) throws IOException {
		givenRequestListApplications(Flux.empty());

		givenRequestPushApplication(
				PushApplicationManifestRequest.builder()
						.manifest(ApplicationManifest.builder()
								.path(resource.getFile().toPath())
								.buildpack(deploymentProperties.getBuildpack())
								.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
								.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
								.environmentVariable("SPRING_APPLICATION_JSON", "{}")
								.healthCheckType(ApplicationHealthCheck.NONE)
								.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
								.name("test-application")
								.noRoute(true)
								.services(Collections.emptySet())
								.build())
						.stagingTimeout(this.deploymentProperties.getStagingTimeout())
						.startupTimeout(this.deploymentProperties.getStartupTimeout())
						.build(), Mono.empty());

		givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
				.diskQuota(0)
				.id("test-application-id")
				.instances(1)
				.memoryLimit(0)
				.name("test-application")
				.requestedState("RUNNING")
				.runningInstances(1)
				.stack("test-stack")
				.build()));

		givenRequestStopApplication("test-application", Mono.error(new UnsupportedOperationException()));
	}


	private void setupTaskWithNonExistentApplicationBindingOneService(Resource resource) throws IOException {
		givenRequestListApplications(Flux.empty());

		givenRequestPushApplication(PushApplicationManifestRequest.builder()
				.manifest(ApplicationManifest.builder()
						.path(resource.getFile().toPath())
						.buildpack(deploymentProperties.getBuildpack())
						.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
						.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
						.environmentVariable("SPRING_APPLICATION_JSON", "{}")
						.healthCheckType(ApplicationHealthCheck.NONE)
						.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
						.name("test-application")
						.noRoute(true)
						.service("test-service-instance-2")
						.build())
				.stagingTimeout(this.deploymentProperties.getStagingTimeout())
				.startupTimeout(this.deploymentProperties.getStartupTimeout())
				.build(), Mono.empty());

		givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
				.diskQuota(0)
				.id("test-application-id")
				.instances(1)
				.memoryLimit(0)
				.name("test-application")
				.requestedState("RUNNING")
				.runningInstances(1)
				.stack("test-stack")
				.build()));

		givenRequestStopApplication("test-application", Mono.empty());

		givenRequestGetApplicationSummary("test-application-id", Mono.just(SummaryApplicationResponse.builder()
				.id("test-application-id")

				.detectedStartCommand("test-command")
				.build()));

		givenRequestCreateTask("test-application-id",
					"test-command",
					this.deploymentProperties.getMemory(),
					"test-application",
					Mono.just(CreateTaskResponse.builder()
				.id("test-task-id")
				.memoryInMb(1024)
				.diskInMb(1024)
				.dropletId("1")
				.createdAt(new Date().toString())
				.updatedAt(new Date().toString())
				.sequenceId(1)
				.name("test-task-id")
				.state(TaskState.SUCCEEDED)
				.build()));
	}

	private void setupTaskWithNonExistentApplicationBindingThreeServices(Resource resource) throws IOException {
		givenRequestListApplications(Flux.empty());

		givenRequestPushApplication(PushApplicationManifestRequest.builder()
				.manifest(ApplicationManifest.builder()
						.path(resource.getFile().toPath())
						.buildpack(deploymentProperties.getBuildpack())
						.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
						.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
						.environmentVariable("SPRING_APPLICATION_JSON", "{}")
						.healthCheckType(ApplicationHealthCheck.NONE)
						.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
						.name("test-application")
						.noRoute(true)
						.service("test-service-instance-1")
						.service("test-service-instance-2")
						.service("test-service-instance-3")
						.build())
				.stagingTimeout(this.deploymentProperties.getStagingTimeout())
				.startupTimeout(this.deploymentProperties.getStartupTimeout())
				.build(), Mono.empty());

		givenRequestGetApplication("test-application", Mono.just(ApplicationDetail.builder()
				.diskQuota(0)
				.id("test-application-id")
				.instances(1)
				.memoryLimit(0)
				.name("test-application")
				.requestedState("RUNNING")
				.runningInstances(1)
				.stack("test-stack")
				.build()));

		givenRequestStopApplication("test-application", Mono.empty());

		givenRequestGetApplicationSummary("test-application-id", Mono.just(SummaryApplicationResponse.builder()
				.id("test-application-id")
				.detectedStartCommand("test-command")
				.build()));

		givenRequestCreateTask("test-application-id",
					"test-command",
					this.deploymentProperties.getMemory(),
					"test-application",
					Mono.just(CreateTaskResponse.builder()
				.id("test-task-id")
				.memoryInMb(1024)
				.diskInMb(1024)
				.dropletId("1")
				.createdAt(new Date().toString())
				.updatedAt(new Date().toString())
				.sequenceId(1)
				.name("test-task-id")
				.state(TaskState.SUCCEEDED)
				.build()));
	}

	public void setupTaskWithNonExistentApplicationRetrievalFails(Resource resource) throws IOException {
		givenRequestListApplications(Flux.empty());

		givenRequestPushApplication(PushApplicationManifestRequest.builder()
				.manifest(ApplicationManifest.builder()
						.path(resource.getFile().toPath())
						.buildpack(deploymentProperties.getBuildpack())
						.command("echo '*** First run of container to allow droplet creation.***' && sleep 300")
						.disk((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getDisk()))
						.environmentVariable("SPRING_APPLICATION_JSON", "{}")
						.healthCheckType(ApplicationHealthCheck.NONE)
						.memory((int) ByteSizeUtils.parseToMebibytes(this.deploymentProperties.getMemory()))
						.name("test-application")
						.noRoute(true)
						.services(Collections.emptySet())
						.build())
				.stagingTimeout(this.deploymentProperties.getStagingTimeout())
				.startupTimeout(this.deploymentProperties.getStartupTimeout())
				.build(), Mono.empty());

		givenRequestStopApplication("test-application", Mono.empty());

		givenRequestGetApplication("test-application", Mono.error(new UnsupportedOperationException()));
	}

	private AppDeploymentRequest defaultRequest() {
		return deploymentRequest(this.resource, Collections.emptyMap());
	}
	private AppDeploymentRequest deploymentRequest(Resource resource, Map<String,String> deploymentProperties) {
		AppDefinition definition = new AppDefinition("test-application", null);
		return new AppDeploymentRequest(definition, resource, deploymentProperties);
	}

	private Mono<ListTasksResponse> runningTasksResponse() {
		List<TaskResource> taskResources = new ArrayList<>();
		for (int i=0; i< TASK_EXECUTION_COUNT; i++) {
			taskResources.add(TaskResource.builder()
				.name("task-" + i)
				.dropletId(UUID.randomUUID().toString())
				.id(UUID.randomUUID().toString())
				.diskInMb(2048)
				.sequenceId(i)
				.state(TaskState.RUNNING)
				.memoryInMb(2048)
				.createdAt(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
				.build());
		}
		ListTasksResponse listTasksResponse = ListTasksResponse.builder().resources(taskResources)
			.pagination(Pagination.builder().totalResults(taskResources.size()).build())
			.build();
		return Mono.just(listTasksResponse);
	}

	private Mono<ListOrganizationsResponse> listOrganizationsResponse() {
		ListOrganizationsResponse response = ListOrganizationsResponse.builder()
		.addAllResources(Collections.<OrganizationResource>singletonList(
				OrganizationResource.builder()
						.metadata(Metadata.builder().id("123").build()).build())
		).build();
		return Mono.just(response);
	}

	private Mono<ListSpacesResponse> listSpacesResponse() {
		ListSpacesResponse response = ListSpacesResponse.builder()
				.addAllResources(Collections.<SpaceResource>singletonList(
						SpaceResource.builder()
								.metadata(Metadata.builder().id("123").build()).build())
				).build();
		return Mono.just(response);
	}
}