package com.sequenceiq.cloudbreak.service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.sequenceiq.ambari.client.AmbariClient;
import com.sequenceiq.cloudbreak.controller.BadRequestException;
import com.sequenceiq.cloudbreak.controller.InternalServerException;
import com.sequenceiq.cloudbreak.domain.Blueprint;
import com.sequenceiq.cloudbreak.domain.Cluster;
import com.sequenceiq.cloudbreak.domain.Stack;
import com.sequenceiq.cloudbreak.domain.Status;
import com.sequenceiq.cloudbreak.repository.ClusterRepository;
import com.sequenceiq.cloudbreak.websocket.WebsocketService;
import com.sequenceiq.cloudbreak.websocket.message.StatusMessage;

import groovyx.net.http.HttpResponseException;

@Service
public class AmbariClusterInstaller {

    private static final double COMPLETED = 100.0;

    private static final Logger LOGGER = LoggerFactory.getLogger(AmbariClusterInstaller.class);

    private static final long POLLING_INTERVAL = 3000;
    private static final int MILLIS = 1000;

    @Autowired
    private WebsocketService websocketService;

    @Autowired
    private ClusterRepository clusterRepository;

    @Async
    public void installAmbariCluster(Stack stack) {
        try {
            Cluster cluster = stack.getCluster();
            LOGGER.info("Trying to install Ambari cluster for stack '{}'", stack.getId());
            if (stack.getCluster() != null && stack.getCluster().getStatus().equals(Status.REQUESTED)) {
                LOGGER.info("Starting Ambari cluster installation for stack '{}' [Ambari server address: {}]", stack.getId(), stack.getAmbariIp());
                cluster.setCreationStarted(new Date().getTime());
                cluster = clusterRepository.save(cluster);
                Blueprint blueprint = cluster.getBlueprint();
                addBlueprint(stack.getAmbariIp(), blueprint);
                AmbariClient ambariClient = new AmbariClient(stack.getAmbariIp(), AmbariClusterService.PORT);
                ambariClient.createCluster(
                        cluster.getName(),
                        blueprint.getBlueprintName(),
                        recommend(stack, ambariClient, blueprint.getBlueprintName())
                        );

                BigDecimal installProgress = new BigDecimal(0);
                while (installProgress.doubleValue() != COMPLETED) {
                    try {
                        Thread.sleep(POLLING_INTERVAL);
                    } catch (InterruptedException e) {
                        LOGGER.info("Interrupted exception occured during polling.", e);
                        Thread.currentThread().interrupt();
                    }
                    installProgress = ambariClient.getInstallProgress();
                    LOGGER.info("Ambari Cluster installing. [Stack: '{}', Cluster: '{}', Progress: {}]", stack.getId(), cluster.getName(), installProgress);
                    // TODO: timeout
                }
                websocketService.send("/topic/cluster", new StatusMessage(cluster.getId(), cluster.getName(), Status.CREATE_COMPLETED.name()));
                cluster.setStatus(Status.CREATE_COMPLETED);
                cluster.setCreationFinished(new Date().getTime());
                clusterRepository.save(cluster);
            } else {
                LOGGER.info("There were no cluster request to this stack, won't install cluster now. [stack: {}]", stack.getId());
            }

        } catch (HttpResponseException e) {
            LOGGER.error("HttpResponseException occured while communicating with Ambari server.", e);
        } catch (Exception e) {
            LOGGER.error("Unhandled exception occured while installing Ambari cluster.", e);
        }
    }

    private Map<String, List<String>> recommend(Stack stack, AmbariClient ambariClient, String blueprintName) {
        Map<String, List<String>> stringListMap = ambariClient.recommendAssignments(blueprintName);
        int nodeCount = 0;
        while (nodeCount != stack.getNodeCount()) {
            nodeCount = 0;
            LOGGER.info("Asking Ambari client to recommend automatic host-hostGroup mapping [Stack: {}, Ambari server address: {}]", stack.getId(),
                    stack.getAmbariIp());
            stringListMap = ambariClient.recommendAssignments(blueprintName);
            try {
                Thread.sleep(MILLIS);
            } catch (InterruptedException e) {
                LOGGER.info("Interrupted exception in recommendation. stackId: {} blueprintName: {} exception: {}", stack.getId(), blueprintName, e);
            }
            for (Map.Entry<String, List<String>> s : stringListMap.entrySet()) {
                nodeCount += s.getValue().size();
            }
            LOGGER.info("Ambari client found {} hosts while trying to recommend assignments [Stack: {}, Ambari server address: {}]",
                    nodeCount, stack.getId(), stack.getAmbariIp());
        }
        return stringListMap;
    }

    public void addBlueprint(String ambariIp, Blueprint blueprint) {
        AmbariClient ambariClient = new AmbariClient(ambariIp, AmbariClusterService.PORT);
        try {
            ambariClient.addBlueprint(blueprint.getBlueprintText());
            LOGGER.info("Blueprint added [Ambari server: {}, blueprint: '{}']", ambariIp, blueprint.getId());
        } catch (HttpResponseException e) {
            if ("Conflict".equals(e.getMessage())) {
                throw new BadRequestException("Ambari blueprint already exists.", e);
            } else if ("Bad Request".equals(e.getMessage())) {
                throw new BadRequestException("Failed to validate Ambari blueprint.", e);
            } else {
                throw new InternalServerException("Something went wrong", e);
            }
        }
    }

}

@@ -33,24 +33,72 @@
 k.service;%0A%0A
+import groovyx.net.http.HttpResponseException;%0A%0A
 import java.
@@ -114,16 +114,16 @@
 ecimal;%0A
-
 import j
@@ -981,56 +981,8 @@
 e;%0A%0A
-import groovyx.net.http.HttpResponseException;%0A%0A
 @Ser
@@ -1075,16 +1075,63 @@
 = 100.0;
+%0A    private static final double FAILED = -1.0;
 %0A%0A    pr
@@ -2662,16 +2662,59 @@
 OMPLETED
+ && installProgress.doubleValue() != FAILED
 ) %7B%0A    
@@ -3294,32 +3294,102 @@
               %7D%0A
+                if (installProgress.doubleValue() == COMPLETED) %7B%0A    
                 
@@ -3524,32 +3524,36 @@
                 
+    
 cluster.setStatu
@@ -3580,16 +3580,20 @@
 LETED);%0A
+    
         
@@ -3663,24 +3663,28 @@
             
+ 
+   
 clusterRepos
@@ -3696,32 +3696,450 @@
 .save(cluster);%0A
+                %7D else if (installProgress.doubleValue() == FAILED) %7B%0A                    websocketService.send(%22/topic/cluster%22, new StatusMessage(cluster.getId(), cluster.getName(), Status.CREATE_FAILED.name()));%0A                    cluster.setStatus(Status.CREATE_FAILED);%0A                    cluster.setCreationFinished(new Date().getTime());%0A                    clusterRepository.save(cluster);%0A                %7D%0A
             %7D el

handle case when ambari install fails