package com.sequenceiq.cloudbreak.service.stack.repair;

import java.util.Set;
import java.util.concurrent.ExecutorService;

import javax.inject.Inject;

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

import com.sequenceiq.cloudbreak.api.model.Status;
import com.sequenceiq.cloudbreak.controller.FlowsAlreadyRunningException;
import com.sequenceiq.cloudbreak.core.flow2.service.ReactorFlowManager;
import com.sequenceiq.cloudbreak.core.flow2.stack.FlowMessageService;
import com.sequenceiq.cloudbreak.core.flow2.stack.Msg;
import com.sequenceiq.cloudbreak.domain.HostMetadata;
import com.sequenceiq.cloudbreak.domain.InstanceMetaData;
import com.sequenceiq.cloudbreak.domain.Stack;
import com.sequenceiq.cloudbreak.repository.HostMetadataRepository;
import com.sequenceiq.cloudbreak.repository.InstanceMetaDataRepository;

@Component
public class StackRepairService {
    private static final Logger LOGGER = LoggerFactory.getLogger(StackRepairService.class);

    @Inject
    private FlowMessageService flowMessageService;

    @Inject
    private InstanceMetaDataRepository instanceMetaDataRepository;

    @Inject
    private HostMetadataRepository hostMetadataRepository;

    @Inject
    private ReactorFlowManager reactorFlowManager;

    @Inject
    private ExecutorService executorService;

    public void add(Stack stack, Set<String> unhealthyInstanceIds) {
        if (unhealthyInstanceIds.isEmpty()) {
            LOGGER.warn("No instances are unhealthy, returning...");
            flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_REPAIR_COMPLETE_CLEAN, Status.AVAILABLE.name());
            return;
        }
        UnhealthyInstances unhealthyInstances = groupInstancesByHostGroups(stack, unhealthyInstanceIds);
        StackRepairFlowSubmitter stackRepairFlowSubmitter = new StackRepairFlowSubmitter(stack.getId(), unhealthyInstances);
        flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_REPAIR_ATTEMPTING, Status.UPDATE_IN_PROGRESS.name());
        executorService.submit(stackRepairFlowSubmitter);
    }

    private UnhealthyInstances groupInstancesByHostGroups(Stack stack, Set<String> unhealthyInstanceIds) {
        UnhealthyInstances unhealthyInstances = new UnhealthyInstances();
        for (String instanceId : unhealthyInstanceIds) {
            InstanceMetaData instanceMetaData = instanceMetaDataRepository.findByInstanceId(stack.getId(), instanceId);
            HostMetadata hostMetadata = hostMetadataRepository.findHostInClusterByName(stack.getCluster().getId(), instanceMetaData.getDiscoveryFQDN());
            String hostGroupName = hostMetadata.getHostGroup().getName();
            unhealthyInstances.addInstance(instanceId, hostGroupName);
        }
        return unhealthyInstances;
    }

    class StackRepairFlowSubmitter implements Runnable {

        private static final int RETRIES = 10;

        private static final int SLEEP_TIME_MS = 1000;

        private final Long stackId;

        private final UnhealthyInstances unhealthyInstances;

        StackRepairFlowSubmitter(Long stackId, UnhealthyInstances unhealthyInstances) {
            this.stackId = stackId;
            this.unhealthyInstances = unhealthyInstances;
        }

        public Long getStackId() {
            return stackId;
        }

        public UnhealthyInstances getUnhealthyInstances() {
            return unhealthyInstances;
        }

        @Override
        public void run() {
            boolean submitted = false;
            int trials = 0;
            while (!submitted) {
                try {
                    reactorFlowManager.triggerStackRepairFlow(stackId, unhealthyInstances);
                    flowMessageService.fireEventAndLog(stackId, Msg.STACK_REPAIR_TRIGGERED, Status.UPDATE_IN_PROGRESS.name());
                    submitted = true;
                } catch (FlowsAlreadyRunningException fare) {
                    trials++;
                    if (trials == RETRIES) {
                        LOGGER.error("Could not submit because other flows are running for stack " + stackId);
                        return;
                    }
                    LOGGER.info("Waiting for other flows of stack " + stackId + " to complete.");
                    try {
                        Thread.sleep(SLEEP_TIME_MS);
                    } catch (InterruptedException e) {
                        LOGGER.error("Interrupted while waiting for other flows to finish.", e);
                        return;
                    }
                }
            }
        }
    }

}
