package io.pivotal.services.plugin.tasks.helper;

import io.pivotal.services.plugin.CfProperties;
import io.pivotal.services.plugin.CfRouteUtil;
import io.pivotal.services.plugin.ImmutableCfProperties;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.Optional;

import static org.cloudfoundry.util.tuple.TupleUtils.function;

/**
 * route -> app
 * <p>
 * Stage 1:
 * Step 1:
 * route-green -&gt; app-green
 * ...testing after stage 1..
 * Stage 2:
 * Step 2:
 * route       -&gt; app-green, app
 * route-green -&gt; app-green
 * <p>
 * Step 3:
 * route       -&gt; app-green
 * route-green -&gt; app-green
 * -&gt; app
 * Step 4:
 * route       -&gt; app-green
 * -&gt; app
 * <p>
 * Step 5:
 * route       -&gt; app-green
 * -&gt; app-blue
 * <p>
 * Step 6:
 * route       -&gt; app
 * -&gt; app-blue
 *
 * @author Biju Kunjummen
 * @author Gabriel Couto
 */
public class CfBlueGreenStage2Delegate {

    private static final Logger LOGGER = Logging
        .getLogger(CfBlueGreenStage2Delegate.class);

    private CfMapRouteDelegate mapRouteDelegate = new CfMapRouteDelegate();
    private CfUnMapRouteDelegate unMapRouteDelegate = new CfUnMapRouteDelegate();
    private CfAppDetailsDelegate appDetailsDelegate = new CfAppDetailsDelegate();
    private CfRenameAppDelegate renameAppDelegate = new CfRenameAppDelegate();
    private CfDeleteAppDelegate deleteAppDelegate = new CfDeleteAppDelegate();
    private CfAppStopDelegate appStopDelegate = new CfAppStopDelegate();

    public Mono<Void> runStage2(Project project, CloudFoundryOperations cfOperations,
                                CfProperties cfProperties) {
        
        String greenNameString = cfProperties.name() + "-green";
        Mono<String> greenRouteMono = CfRouteUtil.getTempRoute(cfOperations, cfProperties, "green");

        CfProperties greenName = ImmutableCfProperties.copyOf(cfProperties)
            .withName(greenNameString);

        Mono<CfProperties> greenNameAndRouteMono = greenRouteMono.map(greenRoute -> ImmutableCfProperties.copyOf(cfProperties)
            .withName(greenNameString)
            .withHost(null)
            .withDomain(null)
            .withRoutes(Collections.singletonList(greenRoute)));

        CfProperties blueName = ImmutableCfProperties.copyOf(cfProperties)
            .withName(cfProperties.name() + "-blue");

        Mono<Optional<ApplicationDetail>> backupAppMono = appDetailsDelegate
            .getAppDetails(cfOperations, blueName);

        Mono<Optional<ApplicationDetail>> existingAppMono = appDetailsDelegate
            .getAppDetails(cfOperations, cfProperties);

        Mono<Void> bgResult = Mono.zip(backupAppMono, existingAppMono, greenNameAndRouteMono, greenRouteMono)
            .flatMap(function((backupApp, existingApp, greenNameAndRoute, greenRouteString) -> {
                LOGGER.lifecycle(
                    "Running Blue Green Deploy - after deploying a 'green' app. App '{}' with route '{}'",
                    cfProperties.name(),
                    greenRouteString);
                return
                    (backupApp.isPresent() ?
                        deleteAppDelegate.deleteApp(cfOperations, blueName) : Mono.empty())
                        .thenMany(mapRouteDelegate.mapRoute(cfOperations, greenName))
                        .thenMany(existingApp.isPresent() ?
                            unMapRouteDelegate.unmapRoute(cfOperations, cfProperties) :
                            Mono.empty())
                        .thenMany(unMapRouteDelegate.unmapRoute(cfOperations, greenNameAndRoute))
                        .then(existingApp.isPresent() ?
                            renameAppDelegate
                                .renameApp(cfOperations, cfProperties, blueName) :
                            Mono.empty())
                        .then(renameAppDelegate
                            .renameApp(cfOperations, greenName, cfProperties))
                        .then(
                            existingApp.isPresent() ?
                                appStopDelegate.stopApp(cfOperations, blueName) :
                                Mono.empty());
            }));

        return bgResult;
    }
}