package com.airlock.iam.flow.infrastructure.intercept;

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.flow.api.domain.model.processor.FlowProcessor;
import com.airlock.iam.flow.api.domain.model.step.FlowStep;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.domain.model.processor.FlowProcessorRunner;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;

public final class PreProcessorInvocationHandler<T extends FlowStep> implements InvocationHandler {
   private final T step;
   private final Collection<FlowProcessor> flowProcessors;

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if (!this.methodReturnsStepResult(method)) {
         return this.callWithExceptionUnwrap(method, args);
      }

      Optional<StepResult> result = FlowProcessorRunner.flowProcessorRunner(this.flowProcessors)
         .preProcess(this.step, (processor, s) -> processor.preStepMethod(s, method.getName(), args == null ? Collections.emptyList() : Arrays.asList(args)));
      return result.isPresent() ? result.get() : this.callWithExceptionUnwrap(method, args);
   }

   private boolean methodReturnsStepResult(Method method) {
      return StepResult.class.isAssignableFrom(method.getReturnType());
   }

   private Object callWithExceptionUnwrap(Method method, Object[] args) throws Throwable {
      try {
         return method.invoke(this.step, args);
      } catch (InvocationTargetException e) {
         throw e.getTargetException();
      } catch (IllegalAccessException | IllegalArgumentException e) {
         throw new ViolationException(e);
      }
   }

   public static <L extends FlowStep> L intercept(L step, Class<L> stepType, Collection<FlowProcessor> flowProcessors) {
      PreProcessorInvocationHandler<L> handler = new PreProcessorInvocationHandler<>(step, flowProcessors);
      return (L)Proxy.newProxyInstance(PreProcessorInvocationHandler.class.getClassLoader(), new Class[]{stepType}, handler);
   }

   @SuppressFBWarnings(justification = "generated code")
   public PreProcessorInvocationHandler(T step, Collection<FlowProcessor> flowProcessors) {
      this.step = step;
      this.flowProcessors = flowProcessors;
   }
}
