// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.provision.maintenance;

import com.yahoo.collections.ListMap;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Deployer;
import com.yahoo.config.provision.Deployment;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.orchestrator.OrchestrationException;
import com.yahoo.vespa.orchestrator.Orchestrator;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.stream.Collectors;

public class RetiredEarlyExpirer extends Maintainer {
    private final Deployer deployer;
    private final Orchestrator orchestrator;

    public RetiredEarlyExpirer(NodeRepository nodeRepository,
                               Zone zone,
                               Duration interval,
                               JobControl jobControl,
                               Deployer deployer,
                               Orchestrator orchestrator) {
        super(nodeRepository, interval, jobControl);
        this.deployer = deployer;
        this.orchestrator = orchestrator;

        List<Zone> applies = Arrays.asList(new Zone(SystemName.cd, Environment.dev, RegionName.from("cd-us-central-1")));
        if (!applies.contains(zone)) {
            String targetZones = applies.stream().map(Zone::toString).collect(Collectors.joining(", "));
            log.info(RetiredEarlyExpirer.class.getName() + " only runs in " + targetZones + ", stopping.");
            deconstruct();
        }
    }

    @Override
    protected void maintain() {
        List<Node> activeNodes = nodeRepository().getNodes(Node.State.active);

        ListMap<ApplicationId, Node> retiredNodesByApplication = new ListMap<>();
        for (Node node : activeNodes) {
            if (node.allocation().isPresent() && node.allocation().get().membership().retired()) {
                retiredNodesByApplication.put(node.allocation().get().owner(), node);
            }
        }

        for (Map.Entry<ApplicationId, List<Node>> entry : retiredNodesByApplication.entrySet()) {
            ApplicationId application = entry.getKey();
            List<Node> retiredNodes = entry.getValue();

            try {
                Optional<Deployment> deployment = deployer.deployFromLocalActive(application, Duration.ofMinutes(30));
                if ( ! deployment.isPresent()) continue; // this will be done at another config server

                List<Node> nodesToRemove = new ArrayList<>();
                for (Node node : retiredNodes) {
                    if (nodeCanBeRemoved(node)) {
                        nodesToRemove.add(node);
                    }
                }

                nodeRepository().setRemovable(application, nodesToRemove);

                deployment.get().activate();

                String nodeList = nodesToRemove.stream().map(Node::hostname).collect(Collectors.joining(", "));
                log.info("Redeployed " + application + " to deactivate retired nodes: " +  nodeList);
            } catch (RuntimeException e) {
                String nodeList = retiredNodes.stream().map(Node::hostname).collect(Collectors.joining(", "));
                log.log(Level.WARNING, "Exception trying to deactivate retired nodes from " + application
                        + ": " + nodeList, e);
            }
        }
    }

    boolean nodeCanBeRemoved(Node node) {
        try {
            orchestrator.acquirePermissionToRemove(new HostName(node.hostname()));
            return true;
        } catch (OrchestrationException e) {
            log.info("Did not get permission to remove retired node " + node + ": " + e.getMessage());
            return false;
        }
    }
}

@@ -343,147 +343,8 @@
 nt;%0A
-import com.yahoo.config.provision.Environment;%0Aimport com.yahoo.config.provision.RegionName;%0Aimport com.yahoo.config.provision.SystemName;%0A
 impo
@@ -379,16 +379,16 @@
 n.Zone;%0A
+
 import c
@@ -701,33 +701,8 @@
 st;%0A
-import java.util.Arrays;%0A
 impo
@@ -1376,24 +1376,24 @@
 = deployer;%0A
+
         this
@@ -1425,420 +1425,8 @@
 tor;
-%0A%0A        List%3CZone%3E applies = Arrays.asList(new Zone(SystemName.cd, Environment.dev, RegionName.from(%22cd-us-central-1%22)));%0A        if (!applies.contains(zone)) %7B%0A            String targetZones = applies.stream().map(Zone::toString).collect(Collectors.joining(%22, %22));%0A            log.info(RetiredEarlyExpirer.class.getName() + %22 only runs in %22 + targetZones + %22, stopping.%22);%0A            deconstruct();%0A        %7D
 %0A   
@@ -3536,21 +3536,16 @@
 retired 
-node 
 %22 + node

Enable RetiredEarlyExpirer in all zones