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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.core.misc.cronto.CrontoHandler;
import com.airlock.iam.core.misc.impl.cronto.CrontoOtpWrongException;
import com.airlock.iam.core.misc.impl.cronto.PendingCrontoTransaction;
import com.airlock.iam.core.misc.impl.cronto.vascocronto.VascoCrontoHandler;
import com.airlock.iam.core.misc.util.ServletUtils;
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 com.google.common.base.MoreObjects;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

public class CrontoTransactionListServlet extends CrontoAppCommunicationServlet {
   private static final ELogger LOG = ELoggerFactory.getLogger(CrontoTransactionListServlet.class);
   private static final String ACTION_TRANSACTION_LIST = "transactionsList";
   private static final String ACTION_PARAM = "action";
   private static final String SERIAL_NUM_PARAM = "serialNumber";
   private static final String SEQUENCE_NUM_PARAM = "sequenceNumber";
   private static final String OTP_PARAM = "otp";

   @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("Discarding transaction list request because there is no Cronto self-service configuration available.");
         formatter.formatErrorResponse(response, this.getTechnicalErrorMessage(request, context));
      } else if (crontoHandler.get() instanceof VascoCrontoHandler vascoCrontoHandler) {
         if (!vascoCrontoHandler.isPushNotificationEnabled()) {
            LOG.info("Discarding transaction list request because the push notification feature is disabled.");
            formatter.formatErrorResponse(response, this.getTechnicalErrorMessage(request, context));
         } else if (!ServletUtils.isPOST(request)) {
            LOG.info("Discarding transaction list request because it is not a POST request.");
            formatter.formatErrorResponse(response, this.getTechnicalErrorMessage(request, context));
         } else {
            Optional<CrontoTransactionListServlet.CrontoTransactionListRequest> transationListRequest = CrontoTransactionListServlet.CrontoTransactionListRequest.of(
               request
            );
            if (transationListRequest.isEmpty()) {
               LOG.info("Ignoring transaction list request because it is invalid.");
               formatter.formatErrorResponse(response, this.getTechnicalErrorMessage(request, context));
            } else {
               try {
                  LOG.debug("Handling transaction list request: " + transationListRequest.get());
                  List<PendingCrontoTransaction> pendingTransactions = vascoCrontoHandler.getPendingTransactions(
                     transationListRequest.get().serialNum, transationListRequest.get().sequenceNum, transationListRequest.get().otp
                  );
                  formatter.formatTransactionListResponse(response, pendingTransactions);
                  LOG.info("Successfully finished handling of transaction list query " + transationListRequest.get() + ".");
               } catch (CrontoOtpWrongException e) {
                  LOG.debug("Invalid Cronto OTP: " + e.getMessage());
                  formatter.formatErrorResponse(response, this.getCrontoOtpErrorMessage(request, context));
               } catch (FailureException e) {
                  LOG.error(
                     com.airlock.iam.elog.api.LogTypes.DATABASE_ACCESS,
                     "Transaction list query failed for request " + transationListRequest.get() + ": " + e.getMessage()
                  );
                  formatter.formatErrorResponse(response, this.getTechnicalErrorMessage(request, context));
               }
            }
         }
      } else {
         LOG.info("CrontoTransactionListServlet can only be used with Vasco Cronto.");
         formatter.formatErrorResponse(response, this.getTechnicalErrorMessage(request, context));
      }
   }

   private String getCrontoOtpErrorMessage(HttpServletRequest request, ConfigurationContext context) {
      return getTranslatedErrorMessage(request, context, "cronto.transaction-list.authentication-failed");
   }

   private String getTechnicalErrorMessage(HttpServletRequest request, ConfigurationContext context) {
      return getTranslatedErrorMessage(request, context, "cronto.transaction-list.technical-error");
   }

   private static final class CrontoTransactionListRequest {
      private final String serialNum;
      private final int sequenceNum;
      private final String otp;

      public static Optional<CrontoTransactionListServlet.CrontoTransactionListRequest> of(HttpServletRequest request) {
         String action = request.getParameter("action");
         if (StringUtils.isEmpty(action)) {
            CrontoTransactionListServlet.LOG.debug("Invalid request: mandatory parameter 'action' is missing or empty");
            return Optional.empty();
         }

         if (!"transactionsList".equals(action)) {
            CrontoTransactionListServlet.LOG.debug("Invalid request: parameter 'action' must be 'transactionsList'");
            return Optional.empty();
         }

         String serialNumber = request.getParameter("serialNumber");
         if (StringUtils.isEmpty(serialNumber)) {
            CrontoTransactionListServlet.LOG.debug("Invalid request: mandatory parameter 'serialNumber' is missing or empty");
            return Optional.empty();
         }

         String sequenceNumberString = request.getParameter("sequenceNumber");
         if (StringUtils.isEmpty(sequenceNumberString)) {
            CrontoTransactionListServlet.LOG.debug("Invalid request: mandatory parameter 'sequenceNumber' is missing or empty");
            return Optional.empty();
         }

         int sequenceNumber;
         try {
            sequenceNumber = Integer.parseInt(sequenceNumberString);
         } catch (NumberFormatException e) {
            CrontoTransactionListServlet.LOG.debug("Invalid request: parameter 'sequenceNumber' is not an integer");
            return Optional.empty();
         }

         String oneTimePassword = request.getParameter("otp");
         if (StringUtils.isEmpty(oneTimePassword)) {
            CrontoTransactionListServlet.LOG.debug("Invalid request: mandatory parameter 'otp' is missing or empty");
            return Optional.empty();
         } else {
            return Optional.of(new CrontoTransactionListServlet.CrontoTransactionListRequest(serialNumber, sequenceNumber, oneTimePassword));
         }
      }

      @Override
      public String toString() {
         return MoreObjects.toStringHelper(this.getClass().getSimpleName()).add("serialNum", this.serialNum).add("sequenceNum", this.sequenceNum).toString();
      }

      @SuppressFBWarnings(justification = "generated code")
      public CrontoTransactionListRequest(String serialNum, int sequenceNum, String otp) {
         this.serialNum = serialNum;
         this.sequenceNum = sequenceNum;
         this.otp = otp;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getSerialNum() {
         return this.serialNum;
      }

      @SuppressFBWarnings(justification = "generated code")
      public int getSequenceNum() {
         return this.sequenceNum;
      }

      @SuppressFBWarnings(justification = "generated code")
      public String getOtp() {
         return this.otp;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof CrontoTransactionListServlet.CrontoTransactionListRequest other)) {
            return false;
         } else if (this.sequenceNum != other.sequenceNum) {
            return false;
         } else {
            Object this$serialNum = this.serialNum;
            Object other$serialNum = other.serialNum;
            if (this$serialNum == null ? other$serialNum == null : this$serialNum.equals(other$serialNum)) {
               Object this$otp = this.otp;
               Object other$otp = other.otp;
               return this$otp == null ? other$otp == null : this$otp.equals(other$otp);
            } else {
               return false;
            }
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         result = result * 59 + this.sequenceNum;
         Object $serialNum = this.serialNum;
         result = result * 59 + ($serialNum == null ? 43 : $serialNum.hashCode());
         Object $otp = this.otp;
         return result * 59 + ($otp == null ? 43 : $otp.hashCode());
      }
   }
}
