package com.airlock.iam.login.app.misc;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.core.domain.model.cronto.CrontoOtp;
import com.airlock.iam.core.domain.model.cronto.challenge.vascocronto.SecureChannelMessage;
import com.airlock.iam.core.misc.cronto.CrontoHandler;
import com.airlock.iam.core.misc.impl.cronto.vascocronto.VascoCrontoOnlineTransaction;
import com.airlock.iam.core.misc.util.ServletUtils;
import com.airlock.iam.core.misc.util.cronto.CrontoUtils;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.login.app.misc.cronto.application.CrontoApplicationResponseFormatter;
import com.airlock.iam.login.app.misc.cronto.application.CrontoMobileApplicationFormatterFactory;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;

public class CrontoOnlineValidationServlet extends CrontoAppCommunicationServlet {
   private static final ELogger LOG = ELoggerFactory.getLogger(CrontoOnlineValidationServlet.class);
   private static final String OTP_PARAMETER = "otp";
   private static final String SERIAL_NR_PARAMATER = "serial";
   private static final String SC_MESSAGE_PARAMETER = "secureChannelMessage";
   private static final String TRANSACTION_ID_PARAMETER = "transactionId";
   private static final String ACTION_PARAMETER = "action";
   private static final String REJECTION_ACTION = "transactionRejection";
   private static final String INVALID_REQUEST_MESSAGE = "Invalid request";

   @Override
   protected void handleCrontoRequest(
      HttpServletRequest request, HttpServletResponse response, ConfigurationContext context, Optional<CrontoHandler> crontoHandler
   ) throws IOException {
      CrontoApplicationResponseFormatter formatter = CrontoMobileApplicationFormatterFactory.forRequest(request);
      if (crontoHandler.isEmpty()) {
         LOG.info("Discard request because there is no Cronto self-service configuration available");
         formatter.formatErrorResponse(response, "Invalid request");
      } else {
         Optional<VascoCrontoOnlineTransaction> crontoResponse = this.readResponse(request);
         if (this.isOnlineValidationEnabled(request, crontoHandler.get()) && !crontoResponse.isEmpty()) {
            LOG.debug("Received Cronto response: " + crontoResponse.get());

            try {
               crontoHandler.get().commitCrontoResponse(crontoResponse.get());
               formatter.formatSuccessResponseWithoutMessage(response);
            } catch (RuntimeException e) {
               LOG.warn(LogTypes.SERVLET_EXCEPTION_HANDLER, "Failed to commit Cronto online response: " + e.getMessage());
               formatter.formatErrorResponse(response, "Invalid request");
            }
         } else {
            LOG.info("Received unexpected or incomplete Cronto response: the request is ignored");
            formatter.formatErrorResponse(response, "Invalid request");
         }
      }
   }

   private Optional<VascoCrontoOnlineTransaction> readResponse(HttpServletRequest request) {
      String serial = request.getParameter("serial");
      if (StringUtils.isEmpty(serial)) {
         LOG.info("Invalid response: mandatory parameter serial is missing or empty");
         return Optional.empty();
      }

      String action = request.getParameter("action");
      String otpParam = request.getParameter("otp");
      Optional<SecureChannelMessage> secureChannelMessage = Optional.ofNullable(request.getParameter("secureChannelMessage"))
         .<String>map(StringUtils::trimToNull)
         .map(SecureChannelMessage::secureChannelMessage);
      String transactionId = request.getParameter("transactionId");
      if (Strings.CI.equals(action, "transactionRejection")) {
         return this.handleRejectedTransaction(serial, secureChannelMessage, transactionId);
      }

      if (StringUtils.isBlank(otpParam)) {
         LOG.info("Invalid response: mandatory parameter otp is missing or empty");
         return Optional.empty();
      }

      CrontoOtp otp = new CrontoOtp(CrontoUtils.sanitizeOnlineValidationOtp(otpParam));
      if (secureChannelMessage.isPresent()) {
         return Optional.of(VascoCrontoOnlineTransaction.scanAndLoginResponse(serial, otp, secureChannelMessage.get()));
      }

      if (StringUtils.isNotEmpty(transactionId)) {
         return Optional.of(VascoCrontoOnlineTransaction.transactionResponse(serial, otp, transactionId));
      }

      LOG.info("Invalid response: one of the parameters secureChannelMessage or transactionId must be present");
      return Optional.empty();
   }

   private Optional<VascoCrontoOnlineTransaction> handleRejectedTransaction(
      String serial, Optional<SecureChannelMessage> secureChannelMessage, String transactionId
   ) {
      if (StringUtils.isNotEmpty(transactionId)) {
         LOG.debug("Received rejection for transaction ID " + LogUtil.quote(transactionId));
         return Optional.of(VascoCrontoOnlineTransaction.rejectedTransactionResponse(serial, transactionId));
      } else if (secureChannelMessage.isPresent()) {
         LOG.debug("Received rejection for secure channel message");
         return Optional.of(VascoCrontoOnlineTransaction.rejectedSecureChannelMessageResponse(serial, secureChannelMessage.get()));
      } else {
         LOG.info("Invalid response: mandatory parameters transactionId or secureChannelMessage are missing or empty");
         return Optional.empty();
      }
   }

   private boolean isOnlineValidationEnabled(HttpServletRequest request, CrontoHandler crontoHandler) {
      if (!ServletUtils.isPOST(request)) {
         LOG.info("Discard request because POST is the only supported method, but got " + request.getMethod());
         return false;
      } else if (!crontoHandler.isOnlineValidationEnabled()) {
         LOG.info("Discard request because Cronto online validation is not enabled.");
         return false;
      } else {
         return true;
      }
   }
}
