package com.sequenceiq.freeipa.flow.freeipa.downscale.handler;

import static com.sequenceiq.cloudbreak.polling.PollingResult.SUCCESS;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.sequenceiq.cloudbreak.common.event.Selectable;
import com.sequenceiq.cloudbreak.orchestrator.exception.CloudbreakOrchestratorException;
import com.sequenceiq.cloudbreak.orchestrator.host.HostOrchestrator;
import com.sequenceiq.cloudbreak.orchestrator.model.GatewayConfig;
import com.sequenceiq.cloudbreak.orchestrator.model.Node;
import com.sequenceiq.cloudbreak.polling.PollingResult;
import com.sequenceiq.cloudbreak.service.CloudbreakException;
import com.sequenceiq.flow.event.EventSelectorUtil;
import com.sequenceiq.flow.reactor.api.handler.EventHandler;
import com.sequenceiq.freeipa.entity.InstanceMetaData;
import com.sequenceiq.freeipa.entity.Stack;
import com.sequenceiq.freeipa.flow.freeipa.downscale.event.DownscaleFailureEvent;
import com.sequenceiq.freeipa.flow.freeipa.downscale.event.removehosts.RemoveHostsFromOrchestrationRequest;
import com.sequenceiq.freeipa.flow.freeipa.downscale.event.removehosts.RemoveHostsFromOrchestrationSuccess;
import com.sequenceiq.freeipa.orchestrator.StackBasedExitCriteriaModel;
import com.sequenceiq.freeipa.service.BootstrapService;
import com.sequenceiq.freeipa.service.GatewayConfigService;
import com.sequenceiq.freeipa.service.stack.StackService;

import reactor.bus.Event;
import reactor.bus.EventBus;

@Component
public class RemoveHostsHandler implements EventHandler<RemoveHostsFromOrchestrationRequest> {
    private static final Logger LOGGER = LoggerFactory.getLogger(RemoveHostsHandler.class);

    @Inject
    private StackService stackService;

    @Inject
    private HostOrchestrator hostOrchestrator;

    @Inject
    private GatewayConfigService gatewayConfigService;

    @Inject
    private EventBus eventBus;

    @Inject
    private BootstrapService bootstrapService;

    @Override
    public String selector() {
        return EventSelectorUtil.selector(RemoveHostsFromOrchestrationRequest.class);
    }

    @Override
    public void accept(Event<RemoveHostsFromOrchestrationRequest> removeHostsRequestEvent) {
        RemoveHostsFromOrchestrationRequest request = removeHostsRequestEvent.getData();
        Set<String> hostNames = request.getHosts();
        Selectable result;
        try {
            if (!hostNames.isEmpty()) {
                Stack stack = stackService.getByIdWithListsInTransaction(request.getResourceId());
                PollingResult orchestratorRemovalPollingResult =
                        removeHostsFromOrchestrator(stack, new ArrayList<>(hostNames));
                if (!PollingResult.isSuccess(orchestratorRemovalPollingResult)) {
                    LOGGER.warn("Can not remove hosts from orchestrator: {}", hostNames);
                }
                // rebootstrap to update the minion's multi-master configuration
                List<String> remainingInstanceIds = stack.getNotDeletedInstanceMetaDataList().stream()
                        .filter(metadata -> Objects.nonNull(metadata.getDiscoveryFQDN()))
                        .filter(metadata -> !hostNames.contains(metadata.getDiscoveryFQDN()))
                        .map(InstanceMetaData::getInstanceId)
                        .collect(Collectors.toList());
                bootstrapService.bootstrap(stack.getId(), remainingInstanceIds);
            }
            result = new RemoveHostsFromOrchestrationSuccess(request.getResourceId());
        } catch (Exception e) {
            LOGGER.error("Failed to remove hosts from orchestration", e);
            result = new DownscaleFailureEvent(request.getResourceId(), "Removing host from orchestration", Set.of(), Map.of(), e);
        }
        eventBus.notify(result.selector(), new Event<>(removeHostsRequestEvent.getHeaders(), result));
    }

    private PollingResult removeHostsFromOrchestrator(Stack stack, List<String> hostNames) throws CloudbreakException {
        LOGGER.debug("Remove hosts from orchestrator: [{}]", hostNames);
        try {
            Map<String, String> removeNodePrivateIPsByFQDN = stack.getAllInstanceMetaDataList().stream()
                    .filter(instanceMetaData -> Objects.nonNull(instanceMetaData.getDiscoveryFQDN()))
                    .filter(instanceMetaData ->
                            hostNames.stream()
                                    .anyMatch(hn -> hn.equals(instanceMetaData.getDiscoveryFQDN())))
                    .collect(Collectors.toMap(instanceMetaData -> instanceMetaData.getDiscoveryFQDN(), instanceMeataData -> instanceMeataData.getPrivateIp()));
            Set<InstanceMetaData> remainingInstanceMetaDatas = stack.getNotDeletedInstanceMetaDataList().stream()
                    .filter(instanceMetaData -> !removeNodePrivateIPsByFQDN.containsValue(instanceMetaData.getPrivateIp()))
                    .collect(Collectors.toSet());
            Set<Node> remainingNodes = remainingInstanceMetaDatas.stream()
                    .map(im -> new Node(im.getPrivateIp(), im.getPublicIp(), im.getInstanceId(),
                            im.getInstanceGroup().getTemplate().getInstanceType(), im.getDiscoveryFQDN(), im.getInstanceGroup().getGroupName()))
                    .collect(Collectors.toSet());
            List<GatewayConfig> remainingGatewayConfigs = gatewayConfigService.getGatewayConfigs(stack, remainingInstanceMetaDatas);
            LOGGER.debug("Tearing down [{}]. The remaining nodes are [{}].", removeNodePrivateIPsByFQDN, remainingNodes);
            hostOrchestrator.tearDown(remainingGatewayConfigs, removeNodePrivateIPsByFQDN, remainingNodes, new StackBasedExitCriteriaModel(stack.getId()));
        } catch (CloudbreakOrchestratorException e) {
            LOGGER.info("Failed to delete orchestrator components while decommissioning: ", e);
            throw new CloudbreakException("Failed to delete orchestrator components while decommissioning: ", e);
        }
        return SUCCESS;
    }
}

@@ -5190,56 +5190,540 @@
 Set%3C
-Node%3E remainingNodes = remainingInstanceMetaData
+InstanceMetaData%3E invalidInstanceMetadata = remainingInstanceMetaDatas.stream()%0A                    .filter(instanceMetaData -%3E Objects.isNull(instanceMetaData.getDiscoveryFQDN()))%0A                    .collect(Collectors.toSet());%0A            Set%3CInstanceMetaData%3E validRemainingNodes = remainingInstanceMetaDatas.stream()%0A                    .filter(instanceMetaData -%3E Objects.nonNull(instanceMetaData.getDiscoveryFQDN()))%0A                    .collect(Collectors.toSet());%0A            Set%3CNode%3E remainingNodes = validRemainingNode
 s.st
@@ -6125,25 +6125,30 @@
 (stack, 
-r
+validR
 emaining
 Instance
@@ -6139,32 +6139,20 @@
 emaining
-InstanceMetaData
+Node
 s);%0A    
@@ -6191,16 +6191,85 @@
 wn %5B%7B%7D%5D.
+ The following were dropped because they did not contain a FQDN %5B%7B%7D%5D.
  The rem
@@ -6292,16 +6292,36 @@
  %5B%7B%7D%5D.%22,
+%0A                   
  removeN
@@ -6332,32 +6332,57 @@
 rivateIPsByFQDN,
+ invalidInstanceMetadata,
  remainingNodes)

Fix downscale repair when FQDN is missing