package com.airlock.iam.admin.application.service.activation;

import com.airlock.iam.base.misc.concurrent.ThreadUtils;
import com.airlock.iam.core.misc.plugin.config.activation.ActivationState;
import com.airlock.iam.core.misc.plugin.config.activation.FileConfigActivationMessenger;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.plugin.framework.misc.AppId;
import com.google.common.annotations.VisibleForTesting;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class FileBasedActivationStateWatcher implements ActivationStateWatcher {
   private static final ELogger LOG = ELoggerFactory.getLogger(FileBasedActivationStateWatcher.class);
   private final Path activationStatesDir;
   private final Duration pollingInterval;
   private final Duration deployTimeout;
   private Instant activationStart;
   private final Collection<AppId> deployedApplicationIds;

   @VisibleForTesting
   FileBasedActivationStateWatcher(Path activationStatesDir, Collection<AppId> deployedApplicationIds, Duration pollingInterval, Duration deployTimeout) {
      this.activationStatesDir = activationStatesDir;
      this.deployedApplicationIds = deployedApplicationIds;
      this.pollingInterval = pollingInterval;
      this.deployTimeout = deployTimeout;
      this.activationStart = Instant.now();
   }

   @Override
   public ActivationStateWatcher.DeploymentResult waitForDeploymentResult() {
      this.waitForDeploymentToCompleteOrTimeout();
      List<ActivationState> states = this.getActivationStates();
      if (DeploymentUtils.deploymentSuccessful(states)) {
         return new ActivationStateWatcher.DeploymentResult(ActivationStateWatcher.ActivationResult.SUCCESS, states);
      } else {
         return this.isAtLeastOneStateOngoing(states)
            ? new ActivationStateWatcher.DeploymentResult(ActivationStateWatcher.ActivationResult.TIMEOUT, states)
            : new ActivationStateWatcher.DeploymentResult(ActivationStateWatcher.ActivationResult.FAILED, states);
      }
   }

   @Override
   public List<ActivationState> getActivationStates() {
      return FileConfigActivationMessenger.readActivationStates(this.activationStatesDir);
   }

   @Override
   public void startActivation() {
      this.activationStart = Instant.now();
      LOG.info(
         "Waiting during "
            + this.deployTimeout.getSeconds()
            + "s for the following application IDs to deploy: "
            + this.deployedApplicationIds.stream().map(AppId::toString).collect(Collectors.joining(", "))
      );
   }

   @VisibleForTesting
   void waitForDeploymentToCompleteOrTimeout() {
      while (this.isOngoing()) {
         this.threadSleep(this.pollingInterval);
      }
   }

   private boolean hasReachedDeploymentTimeout() {
      return Instant.now().isAfter(this.activationStart.plus(this.deployTimeout));
   }

   private boolean isAtLeastOneStateOngoing(Collection<ActivationState> activationStates) {
      return DeploymentUtils.deploymentOngoing(activationStates);
   }

   @Override
   public boolean isOngoing() {
      if (this.hasReachedDeploymentTimeout()) {
         return false;
      }

      List<ActivationState> activationStates = this.getActivationStates();
      return !this.areAllExpectedActivationStatesPresent(activationStates) ? true : this.isAtLeastOneStateOngoing(activationStates);
   }

   private boolean areAllExpectedActivationStatesPresent(Collection<ActivationState> activationStates) {
      Collection<AppId> appIds = ActivationStateWatcher.statesToAppIds(activationStates);
      return appIds.containsAll(this.deployedApplicationIds);
   }

   void threadSleep(Duration duration) {
      ThreadUtils.sleepInterruptibly(duration);
   }
}
