package com.airlock.iam.flow.shared.infrastructure.service;

import com.airlock.iam.authentication.api.domain.model.step.AuthenticationStepResultBuilder;
import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.misc.exception.Exceptions;
import com.airlock.iam.common.api.domain.model.exception.EmailServerUnavailableException;
import com.airlock.iam.common.api.domain.model.exception.ExternalServiceUnavailableException;
import com.airlock.iam.common.api.domain.model.exception.FuturaeServerUnavailableException;
import com.airlock.iam.common.api.domain.model.exception.RadiusServerUnavailableException;
import com.airlock.iam.common.api.domain.model.exception.SmsGatewayUnavailableException;
import com.airlock.iam.elog.api.ELogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.elog.api.LogTypes;
import com.airlock.iam.flow.api.domain.model.step.FlowStep;
import com.airlock.iam.flow.api.domain.model.step.GenericStepError;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.api.domain.model.step.StepResultBuilder;
import com.airlock.iam.flow.domain.model.statemachine.runtime.StepExceptionHandler;

public class StepExceptionHandlerImpl implements StepExceptionHandler {
   private static final ELogger LOG = ELoggerFactory.getLogger(StepExceptionHandlerImpl.class);

   @Override
   public StepResult handleExceptionFromStep(Exception exception, FlowStep step) {
      Exception unwrapped = this.unwrapProvisionException(exception);

      return switch (unwrapped) {
         case ViolationException violationException -> handleViolationException(violationException, step);
         case ExternalServiceUnavailableException externalServiceException -> handleExternalServiceException(externalServiceException, step);
         case ServiceConfigurationException serviceConfigurationException -> handleServiceConfigurationException(serviceConfigurationException, step);
         default -> handleOtherException(exception, step);
      };
   }

   private Exception unwrapProvisionException(Exception exception) {
      return Exceptions.findCause(exception, ViolationException.class)
         .map(Exception.class::cast)
         .or(() -> Exceptions.findCause(exception, ServiceConfigurationException.class))
         .orElse(exception);
   }

   private static StepResult handleExternalServiceException(ExternalServiceUnavailableException exception, FlowStep step) {
      ELogType logType = switch (exception) {
         case EmailServerUnavailableException var7 -> LogTypes.EMAIL_SERVER_ACCESS;
         case RadiusServerUnavailableException var8 -> LogTypes.RADIUS_SERVER_ACCESS;
         case SmsGatewayUnavailableException var9 -> LogTypes.SMS_GATEWAY;
         case FuturaeServerUnavailableException var10 -> LogTypes.FUTURAE_SERVER_ACCESS;
         default -> LogTypes.EXTERNAL_SERVICE_ACCESS;
      };
      LOG.fatal(logType, String.format("Exception from calling an external service at %s: %s", exception.getLogSafeUri(), exception.getMessage()));
      LOG.debug("Stacktrace: ", exception);
      StepResultBuilder builder = step.stepResultFactory().internalError(GenericStepError.EXTERNAL_SERVICE_UNAVAILABLE);
      if (builder instanceof AuthenticationStepResultBuilder authBuilder) {
         authBuilder.isFailedFactor(false);
      }

      return builder.build();
   }

   private static StepResult handleServiceConfigurationException(ServiceConfigurationException exception, FlowStep step) {
      LOG.warn(com.airlock.iam.common.infrastructure.logger.LogTypes.REQUEST_ERROR, "Feature not configured: " + exception.getMessage());
      return step.stepResultFactory().internalError(GenericStepError.NOT_IMPLEMENTED).build();
   }

   private static StepResult handleViolationException(ViolationException exception, FlowStep step) {
      LOG.debug("ViolationException from step: ", exception);
      return step.stepResultFactory()
         .failDueToUnexpectedCall(String.format("ViolationException from step is handled as unexpected call: %s", exception.getMessage()))
         .build();
   }

   private static StepResult handleOtherException(Exception exception, FlowStep step) {
      LOG.warn(com.airlock.iam.common.infrastructure.logger.LogTypes.REQUEST_ERROR, "Exception during flow step method execution: " + exception.getMessage());
      LOG.debug("Stacktrace: ", exception);
      return step.stepResultFactory().internalError().build();
   }
}
