package fi.vincit.multiusertest.rule.expectation2;

import fi.vincit.multiusertest.rule.AuthorizationRule;
import fi.vincit.multiusertest.util.UserIdentifier;
import fi.vincit.multiusertest.util.UserIdentifierCollection;
import fi.vincit.multiusertest.util.UserIdentifiers;

import java.util.*;

public abstract class AbstractWhenThen<T extends TestExpectation> implements WhenThen<T> {

    private final Set<UserIdentifier> currentIdentifiers = new HashSet<>();
    private final Map<UserIdentifier, T> expectationsByIdentifier = new HashMap<>();

    private final UserIdentifier userIdentifier;
    private final AuthorizationRule authorizationRule;
    private T defaultExpectation;

    public AbstractWhenThen(UserIdentifier userIdentifier, AuthorizationRule authorizationRule) {
        this.userIdentifier = userIdentifier;
        this.authorizationRule = authorizationRule;
    }

    @Override
    public WhenThen<T> whenCalledWith(UserIdentifiers... userIdentifiers) {
        currentIdentifiers.clear();

        if (userIdentifiers.length == 0) {
            throw new IllegalArgumentException("At least one identifier must be defined");
        }

        for (UserIdentifiers identifiers : userIdentifiers) {
            identifiers.getIdentifiers().forEach(this::addCurrentUserIdentifiers);
        }
        return this;
    }

    @Override
    public Then<T> whenCalledWithAnyOf(UserIdentifierCollection... userIdentifiers) {
        return whenCalledWith(UserIdentifiers.anyOf(userIdentifiers));
    }

    @Override
    public Then<T> whenCalledWithAnyOf(String... userIdentifiers) {
        return whenCalledWith(UserIdentifiers.anyOf(userIdentifiers));
    }

    @Override
    public Then<T> whenCalledWith(UserIdentifier... userIdentifiers) {
        currentIdentifiers.clear();

        if (userIdentifiers.length == 0) {
            throw new IllegalArgumentException("At least one identifier must be defined");
        }

        addCurrentUserIdentifiers(userIdentifiers);

        return this;
    }

    private void addCurrentUserIdentifiers(UserIdentifier... userIdentifiers) {
        for (UserIdentifier identifier : userIdentifiers) {
            if (currentIdentifiers.contains(identifier)) {
                throw new IllegalStateException("User identifier " + identifier + " already set");
            }
            currentIdentifiers.add(identifier);
        }
    }

    @Override
    public WhenThen<T> then(final T testExpectation) {
        Objects.requireNonNull(testExpectation, "testExpectation must not be null");

        if (currentIdentifiers.isEmpty()) {
            throw new IllegalStateException("Call whenCalledWith before calling then method");
        }

        try {
            currentIdentifiers.forEach(identifier -> {
                if (expectationsByIdentifier.containsKey(identifier)) {
                    throw new IllegalStateException(
                            String.format("User identifier %s already has expectation",
                                    identifier.toString()
                            )
                    );
                } else {
                    expectationsByIdentifier.put(
                            identifier,
                            testExpectation
                    );
                }
            });
        } finally {
            currentIdentifiers.clear();
        }

        return this;
    }

    @Override
    public WhenThen<T> otherwise(T testExpectation) {
        Objects.requireNonNull(testExpectation, "testExpectation must not be null");
        this.defaultExpectation = testExpectation;
        return this;
    }

    @Override
    public WhenThen<T> byDefault(T testExpectation) {
        return otherwise(testExpectation);
    }

    @Override
    public void test() throws Throwable {
        T testExpectation = expectationsByIdentifier.computeIfAbsent(
                userIdentifier,
                this::getDefinedDefaultException
        );

        this.authorizationRule.markExpectationConstructed();
        this.test(testExpectation, userIdentifier);
    }

    protected abstract void test(T testExpectation, UserIdentifier userIdentifier) throws Throwable;

    protected abstract T getDefaultExpectation(UserIdentifier userIdentifier);

    protected T getDefinedDefaultException(UserIdentifier userIdentifier) {
        return Optional.ofNullable(defaultExpectation)
                .orElseGet(() -> getDefaultExpectation(userIdentifier));
    }

    /*
    For unit tests
    */

    Set<UserIdentifier> getCurrentIdentifiers() {
        return currentIdentifiers;
    }

    Map<UserIdentifier, T> getExpectationsByIdentifier() {
        return expectationsByIdentifier;
    }

}

@@ -1024,143 +1024,38 @@
     
-if (userIdentifiers.length == 0) %7B%0A            throw new IllegalArgumentException(%22At least one identifier must be defined%22);%0A        %7D
+validateWhen(userIdentifiers);
 %0A%0A  
@@ -1227,32 +1227,275 @@
 rn this;%0A    %7D%0A%0A
+    @Override%0A    public Then%3CT%3E whenCalledWith(UserIdentifier... userIdentifiers) %7B%0A        currentIdentifiers.clear();%0A%0A        validateWhen(userIdentifiers);%0A%0A        addCurrentUserIdentifiers(userIdentifiers);%0A%0A        return this;%0A    %7D%0A%0A
     @Override%0A  
@@ -1820,356 +1820,8 @@
  %7D%0A%0A
-    @Override%0A    public Then%3CT%3E whenCalledWith(UserIdentifier... userIdentifiers) %7B%0A        currentIdentifiers.clear();%0A%0A        if (userIdentifiers.length == 0) %7B%0A            throw new IllegalArgumentException(%22At least one identifier must be defined%22);%0A        %7D%0A%0A        addCurrentUserIdentifiers(userIdentifiers);%0A%0A        return this;%0A    %7D%0A%0A
     
@@ -4284,24 +4284,239 @@
 r));%0A    %7D%0A%0A
+    private static %3CT%3E void validateWhen(T%5B%5D userIdentifiers) %7B%0A        if (userIdentifiers.length == 0) %7B%0A            throw new IllegalArgumentException(%22At least one identifier must be defined%22);%0A        %7D%0A    %7D%0A%0A
     /*%0A    F

Refactor AbstractWhenThen