package fi.vincit.multiusertest.util;

import fi.vincit.multiusertest.annotation.MultiUserConfigClass;
import fi.vincit.multiusertest.test.MultiUserConfig;
import fi.vincit.multiusertest.test.UserRoleIT;
import org.junit.Before;
import org.junit.internal.runners.statements.RunBefores;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;

/**
 * Helper class for delegating calls from JUnit runner.
 * Does some required method filtering and helps executing
 * Before methods in correct order.
 */
public class RunnerDelegate {

    private final UserIdentifier producerIdentifier;
    private final UserIdentifier userIdentifier;
    private final TestMethodFilter shouldRunChecker;

    public RunnerDelegate(UserIdentifier producerIdentifier, UserIdentifier consumerIdentifier) {
        Objects.requireNonNull(producerIdentifier);
        Objects.requireNonNull(consumerIdentifier);

        this.producerIdentifier = producerIdentifier;
        this.userIdentifier = consumerIdentifier;
        this.shouldRunChecker = new TestMethodFilter(producerIdentifier, consumerIdentifier);
    }

    // Only for testing
    RunnerDelegate(UserIdentifier producerIdentifier, UserIdentifier userIdentifier, TestMethodFilter shouldRunChecker) {
        Objects.requireNonNull(producerIdentifier);
        Objects.requireNonNull(userIdentifier);
        Objects.requireNonNull(shouldRunChecker);

        this.producerIdentifier = producerIdentifier;
        this.userIdentifier = userIdentifier;
        this.shouldRunChecker = shouldRunChecker;
    }

    public List<FrameworkMethod> filterMethods(List<FrameworkMethod> methods) {
        List<FrameworkMethod> filteredMethods = shouldRunChecker.filter(methods);
        if (!filteredMethods.isEmpty()) {
            return filteredMethods;
        } else {
            return methods;
        }
    }

    public boolean isIgnored(FrameworkMethod child, boolean isIgnoredByParent) {
        return !shouldRunChecker.shouldRun(child) || isIgnoredByParent;
    }

    public String testName(FrameworkMethod method) {
        return String.format("%s", method.getName());
    }

    public String getName(TestClass testClass) {
        return String.format("%s", getIdentifierDescription());
    }

    private String getIdentifierDescription() {
        return String.format("producer={%s}, consumer={%s}", producerIdentifier, userIdentifier);
    }

    public Object createTest(Object testInstance) {

        if (testInstance instanceof UserRoleIT) {
            UserRoleIT roleItInstance = (UserRoleIT) testInstance;
            roleItInstance.setUsers(producerIdentifier, userIdentifier);
            return roleItInstance;
        } else {
            return testInstance;
        }

    }

    private MultiUserConfig getConfigComponent(Object testInstance) {
        Optional<MultiUserConfig> config = Optional.empty();
        try {
            for (Field field : testInstance.getClass().getDeclaredFields()) {
                if (field.isAnnotationPresent(MultiUserConfigClass.class)) {
                    field.setAccessible(true);
                    config = Optional.ofNullable((MultiUserConfig) field.get(testInstance));
                    field.setAccessible(false);
                    break;
                }
            }

            if (config.isPresent()) {
                return config.get();
            } else {
                throw new IllegalStateException("MultiUserConfigClass not found on " + testInstance.getClass().getSimpleName());
            }
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    public Statement withBefores(final TestClass testClass, final Object target, final Statement statement) {
        List<FrameworkMethod> befores = testClass.getAnnotatedMethods(
                Before.class);
        final Statement runLoginBeforeTestMethod = new Statement() {
            @Override
            public void evaluate() throws Throwable {
                preEvaluateConfig();
                statement.evaluate();
            }

            private void preEvaluateConfig() {
                UserRoleIT userRoleIt = null;
                if (target instanceof UserRoleIT) {
                    userRoleIt = (UserRoleIT) target;
                } else {
                    userRoleIt = getConfigComponent(target);
                    userRoleIt.setUsers(producerIdentifier, userIdentifier);
                }

                userRoleIt.logInAs(LoginRole.PRODUCER);
            }
        };
        return befores.isEmpty() ? runLoginBeforeTestMethod : new RunBefores(runLoginBeforeTestMethod,
                befores, target);


    }
}

@@ -2913,32 +2913,69 @@
 %0A        %7D else 
+if (hasComponentConfig(testInstance))
 %7B%0D%0A            r
@@ -3000,26 +3000,497 @@
 ;%0D%0A        %7D
-%0D%0A
+ else %7B%0D%0A            throw new IllegalStateException(%22Invalid test configuration. %22 +%0D%0A                    %22Test has to extend UserRoleIT or have configuration annotated with @MultiUserConfigClass.%22);%0D%0A        %7D%0D%0A%0D%0A    %7D%0D%0A%0D%0A    private boolean hasComponentConfig(Object testInstance) %7B%0D%0A        try %7B%0D%0A            return findFieldWithConfig(testInstance).isPresent();%0D%0A        %7D catch (IllegalAccessException e) %7B%0D%0A            throw new IllegalStateException(e);%0D%0A        %7D
 %0D%0A    %7D%0D%0A%0D%0A 
@@ -3652,151 +3652,158 @@
     
-for (
+Optional%3C
 Field
+%3E
  field 
-: testInstance.getClass().getDeclaredFields()) %7B%0D%0A                if (field.isAnnotationPresent(MultiUserConfigClass.class)) %7B
+= findFieldWithConfig(testInstance);%0D%0A%0D%0A            if (field.isPresent()) %7B%0D%0A                Field fieldInstance = field.get();
 %0D%0A  
@@ -3812,33 +3812,37 @@
             
-    
 field
+Instance
 .setAccessib
@@ -3848,28 +3848,24 @@
 ble(true);%0D%0A
-    
             
@@ -3916,24 +3916,32 @@
 onfig) field
+Instance
 .get(testIns
@@ -3966,25 +3966,29 @@
         
-    
 field
+Instance
 .setAcce
@@ -4007,55 +4007,8 @@
 );%0D%0A
-                    break;%0D%0A                %7D%0D%0A
     
@@ -4371,32 +4371,396 @@
     %7D%0D%0A    %7D%0D%0A%0D%0A
+    private Optional%3CField%3E findFieldWithConfig(Object testInstance) throws IllegalAccessException %7B%0D%0A        for (Field field : testInstance.getClass().getDeclaredFields()) %7B%0D%0A            if (field.isAnnotationPresent(MultiUserConfigClass.class)) %7B%0D%0A                return Optional.of(field);%0D%0A            %7D%0D%0A        %7D%0D%0A        return Optional.empty();%0D%0A    %7D%0D%0A%0D%0A
     public State

Fix config validation