package com.guidewire.pl.system.servlet;

import com.google.common.annotations.VisibleForTesting;
import com.guidewire.pl.system.dependency.PLDependencies;
import com.guidewire.pl.system.server.InitTab;
import com.guidewire.pl.system.server.RunlevelAdapter;
import com.guidewire.pl.system.server.InitTab.RunlevelTransition;
import gw.api.system.server.Runlevel;
import gw.api.system.server.ServerModeUtil;
import gw.pl.util.ExceptionUtil;
import java.io.IOException;
import java.util.Arrays;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.json.JSONObject;

public class PingServerServlet extends HttpServlet {
   public static final String NOT_NULL = "<not null>";
   public static final String SHOW_STACK_SYS_PROP_NAME = "gw.ping.servlet.show.stack";
   public static final String RUN_LEVEL_ORDINAL_KEY = "runLevelOrdinal";
   public static final String RUN_LEVEL_NAME_KEY = "runLevelName";
   public static final String RUN_LEVEL_CODE_KEY = "runLevelCode";
   public static final String STARTUP_EXCEPTION_KEY = "startupException";
   public static final String SHARD_ID_KEY = "shardId";
   public static final String SERVER_ID_KEY = "serverId";
   public static final String UPTIME_SECONDS_KEY = "uptimeSeconds";
   public static final String PLANNED_SHUTDOWN_STATUS_KEY = "plannedShutdownStatus";
   public static final String ATTEMPTING_TRANSITION_KEY = "attemptingTransition";
   public static final String FROM_RUN_LEVEL_ORDINAL_KEY = "fromRunLevelOrdinal";
   public static final String FROM_RUN_LEVEL_NAME_KEY = "fromRunLevelName";
   public static final String TO_RUN_LEVEL_ORDINAL_KEY = "toRunLevelOrdinal";
   public static final String TO_RUN_LEVEL_NAME_KEY = "toRunLevelName";
   public static final String THREAD_STACK_TRACE_KEY = "threadStackTrace";
   public static final String PLANNED_SHUTDOWN_STATUS_READY = "ready";
   public static final String PLANNED_SHUTDOWN_STATUS_ACTIVATED = "activated";

   protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      if ("2".equalsIgnoreCase(request.getParameter("v"))) {
         response.setContentType("application/json");
         response.getWriter().print(buildInitTabState().toString(2));
      } else {
         int ping = RunlevelAdapter.itoe(InitTab.getInstance().getRunLevel());
         response.getWriter().write(ping);
      }
   }

   @Nonnull
   @VisibleForTesting
   public static JSONObject buildInitTabState() {
      boolean showStacks = ServerModeUtil.isDev() || ServerModeUtil.isTest() || "true".equalsIgnoreCase(System.getProperty("gw.ping.servlet.show.stack"));
      InitTab initTab = InitTab.getInstance();
      RunlevelTransition transition = initTab.getAttemptingTransition();
      Runlevel runLevel = initTab.getRunLevelNoLock();
      Throwable startupException = initTab.getStartupException();
      JSONObject json = new JSONObject();
      json.put("runLevelOrdinal", runLevel.ordinal());
      json.put("runLevelName", runLevel.name());
      json.put("runLevelCode", RunlevelAdapter.itoe(runLevel));
      json.put("serverId", PLDependencies.getEnvironment().getServerId());
      putIfNonEmpty(json, "shardId", PLDependencies.getEnvironment().getShardId());
      json.put("uptimeSeconds", initTab.getUptimeSeconds());
      boolean plannedShutdownScheduled = PLDependencies.getPlannedShutdownManager().isPlannedShutdownScheduled();
      if (plannedShutdownScheduled && PLDependencies.getPlannedShutdownManager().isReadyForShutdown()) {
         json.put("plannedShutdownStatus", "ready");
      } else if (plannedShutdownScheduled) {
         json.put("plannedShutdownStatus", "activated");
      }

      if (transition != null) {
         JSONObject transitionJson = new JSONObject();
         json.put("attemptingTransition", transitionJson);
         transitionJson.put("fromRunLevelOrdinal", transition.getFrom().ordinal());
         transitionJson.put("fromRunLevelName", transition.getFrom().name());
         transitionJson.put("toRunLevelOrdinal", transition.getTo().ordinal());
         transitionJson.put("toRunLevelName", transition.getTo().name());
         if (showStacks) {
            transitionJson.put("threadStackTrace", threadStateToString(transition.getThread()));
         }
      }

      if (startupException != null) {
         json.put("startupException", showStacks ? ExceptionUtil.getCompleteStackTrace(startupException) : "<not null>");
      }

      return json;
   }

   private static JSONObject putIfNonEmpty(JSONObject json, String name, String value) {
      if (!StringUtils.isEmpty(value)) {
         json.put(name, value);
      }

      return json;
   }

   private static String threadStateToString(Thread thread) {
      return thread.getName()
         + ":"
         + thread.getState().toString()
         + "\n\tat  "
         + Arrays.asList(thread.getStackTrace()).stream().map(StackTraceElement::toString).collect(Collectors.joining("\n\tat "));
   }
}
