package io.arivera.oss.embedded.rabbitmq.helpers;

import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig;
import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCommandException;
import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCtl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zeroturnaround.exec.ProcessResult;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * A helper class used to shut down a specific RabbitMQ Process and wait until it's the process is stopped.
 */
public class ShutdownHelper implements Runnable {

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

  private final EmbeddedRabbitMqConfig config;
  private final Future<ProcessResult> rabbitMqProcess;
  private final long timeoutDuration;
  private final TimeUnit timeoutUnit;

  /**
   * Constructs a new instance that will be used to shut down the given RabbitMQ server process.
   */
  public ShutdownHelper(EmbeddedRabbitMqConfig config, Future<ProcessResult> rabbitMqProcess) {
    this.config = config;
    this.rabbitMqProcess = rabbitMqProcess;
    this.timeoutDuration = config.getDefaultRabbitMqCtlTimeoutInMillis();
    this.timeoutUnit = TimeUnit.MILLISECONDS;
  }

  @Override
  public void run() throws ShutDownException {
    submitShutdownRequest();
    confirmShutdown();
  }

  private void submitShutdownRequest() throws ShutDownException {
    Future<ProcessResult> resultFuture;
    try {
      resultFuture = new RabbitMqCtl(config).stop();
    } catch (RabbitMqCommandException e) {
      throw new ShutDownException("Could not successfully execute command to stop RabbitMQ Server", e);
    }

    int exitValue;
    try {
      ProcessResult rabbitMqCtlProcessResult = resultFuture.get(timeoutDuration, timeoutUnit);
      exitValue = rabbitMqCtlProcessResult.getExitValue();
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
      throw new ShutDownException("Error while waiting " + timeoutDuration + " " + timeoutUnit + " for command "
          + "to shut down RabbitMQ Server to finish", e);
    }

    if (exitValue == 0) {
      LOGGER.debug("Successfully commanded RabbitMQ Server to stop.");
    } else {
      LOGGER.warn("Command to stop RabbitMQ Sever failed with exit value: " + exitValue);
    }
  }

  private void confirmShutdown() throws ShutDownException {
    int exitValue;
    try {
      ProcessResult rabbitMqProcessResult = rabbitMqProcess.get(timeoutDuration, TimeUnit.MILLISECONDS);
      exitValue = rabbitMqProcessResult.getExitValue();
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
      throw new ShutDownException("Error while waiting " + timeoutDuration + " " + timeoutUnit + "for "
          + "RabbitMQ Server to shut down", e);
    }

    if (exitValue == 0) {
      LOGGER.debug("RabbitMQ Server stopped successfully.");
    } else {
      LOGGER.warn("RabbitMQ Server stopped with exit value: " + exitValue);
    }
  }
}