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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
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.domain.model.cronto.challenge.vascocronto.VascoCrontoActivation2Challenge;
import com.airlock.iam.core.misc.cronto.CrontoHandler;
import com.airlock.iam.core.misc.impl.cronto.CrontoActivationSession;
import com.airlock.iam.core.misc.impl.cronto.vascocronto.VascoCrontoDeviceInfo;
import com.airlock.iam.core.misc.impl.cronto.vascocronto.VascoCrontoHandler;
import com.airlock.iam.core.misc.impl.token.cronto.vascocronto.VascoCrontoOnlineActivationToken;
import com.airlock.iam.core.misc.util.vasco.VascoActivation2Challenge;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.log.audit.AuditLogger;
import com.airlock.iam.log.usertrail.UserTrailLogger;
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;

public final class CrontoOnlinePostActivationServlet extends CrontoAppCommunicationServlet {
   private static final ELogger LOG = ELoggerFactory.getLogger(CrontoOnlinePostActivationServlet.class);
   private static final String OTP_PARAMETER = "otp";
   private static final String DEV_PARAMETER = "deviceIdentifier";
   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 if (crontoHandler.get() instanceof VascoCrontoHandler vascoCrontoHandler) {
         if (!isOnlineActivationEnabled(vascoCrontoHandler)) {
            LOG.info("Received unexpected or incomplete Cronto response: the request is ignored");
            formatter.formatErrorResponse(response, "Invalid request");
         } else {
            String otp = request.getParameter("otp");
            String deviceIdentifier = request.getParameter("deviceIdentifier");
            if (otp != null && deviceIdentifier != null) {
               try {
                  VascoCrontoOnlineActivationToken activationToken = vascoCrontoHandler.getVascoCrontoTokenManager()
                     .retrieveCrontoOnlineActivationByDeviceIdentifier(deviceIdentifier);
                  CrontoActivationSession session = getCrontoActivationSessionFromToken(vascoCrontoHandler, activationToken);
                  vascoCrontoHandler.verifyActivationResponse2(session, new CrontoOtp(otp));
                  if (session.isResponse2Valid()) {
                     LOG.info("Challenge validated.");
                     vascoCrontoHandler.addActivatedDevice(session, "");
                     vascoCrontoHandler.getVascoCrontoTokenManager().deleteOnlineActivationToken(activationToken);
                     String deviceId = session.getDeviceInfo().getDeviceId().getDisplayId();
                     UserTrailLogger.log(
                        session.getUserId().getValue(), "Successfully completed online activation of Cronto device " + LogUtil.logSafeQuote(deviceId) + "."
                     );
                     AuditLogger.log(
                        this.getClass(),
                        "User "
                           + LogUtil.quote(session.getUserId())
                           + " activated Cronto device "
                           + LogUtil.logSafeQuote(deviceId)
                           + " using online activation."
                     );
                     formatter.formatSuccessResponse(response);
                  } else {
                     LOG.info("OTP invalid.");
                     formatter.formatErrorResponse(response, "Invalid request");
                  }
               } catch (FailureException e) {
                  LOG.error(com.airlock.iam.elog.api.LogTypes.ILLEGAL_ARGUMENT, "Failure while verifying response message 2 failed: " + e.getMessage());
                  formatter.formatErrorResponse(response, "Invalid request");
               }
            } else {
               LOG.info("Device Identifier or OTP missing in request.");
               formatter.formatErrorResponse(response, "Invalid request");
            }
         }
      } else {
         LOG.info("Discard request because configured cronto handler is not a vasco cronto handler.");
         formatter.formatErrorResponse(response, "Invalid request");
      }
   }

   private static CrontoActivationSession getCrontoActivationSessionFromToken(
      VascoCrontoHandler vascoCrontoHandler, VascoCrontoOnlineActivationToken activationToken
   ) {
      VascoCrontoDeviceInfo deviceInfo = getVascoCrontoDeviceInfoFromToken(vascoCrontoHandler, activationToken);
      VascoCrontoActivation2Challenge vascoCrontoActivation2Challenge = new VascoCrontoActivation2Challenge(
         SecureChannelMessage.secureChannelMessage(activationToken.getChallenge())
      );
      return CrontoActivationSession.restoreSession(activationToken.getUserId(), deviceInfo, vascoCrontoActivation2Challenge);
   }

   private static VascoCrontoDeviceInfo getVascoCrontoDeviceInfoFromToken(VascoCrontoHandler vch, VascoCrontoOnlineActivationToken activationToken) {
      VascoCrontoDeviceInfo deviceInfo = new VascoCrontoDeviceInfo(
         "idDuringActivation", activationToken.getAccountId().getId(), activationToken.getPlatformCode()
      );
      if (activationToken.getOtpBlob() != null) {
         deviceInfo.setOtpBlob(
            Optional.of(
               new VascoActivation2Challenge.VascoBlob(
                  vch.getVascoHandler().decryptBlobData(activationToken.getOtpBlob()), "serial", activationToken.getCryptoApplIndex()
               )
            )
         );
      }

      if (activationToken.getDeviceId().getSequenceNum().isPresent()) {
         deviceInfo.setSequenceNum(activationToken.getDeviceId().getSequenceNum().get());
      } else {
         LOG.info("No sequence number found.");
      }

      deviceInfo.setSecureChannelBlob(
         new VascoActivation2Challenge.VascoBlob(
            vch.getVascoHandler().decryptBlobData(activationToken.getDelegate().getTokenData()), "serial", activationToken.getCryptoApplIndex()
         )
      );
      return deviceInfo;
   }

   private static boolean isOnlineActivationEnabled(VascoCrontoHandler crontoHandler) {
      if (!crontoHandler.isOnlineActivationEnabled()) {
         LOG.info("Discard request because Cronto online activation is not enabled.");
         return false;
      } else {
         return true;
      }
   }
}
