package it.com.atlassian.bitbucket.integrity;

import com.google.common.base.Joiner;
import it.com.atlassian.bitbucket.integrity.annotation.Ahead;
import it.com.atlassian.bitbucket.integrity.annotation.Behind;
import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.MessageFormat;
import java.util.EnumSet;

/**
 * A Rule to assist with choosing which tests to run depending on the way the ZDB was restored.
 */
public class RestoreIgnoreRule implements TestRule {

    /**
     * The restore type refers to the state of the filesystem.
     * BEHIND indicates the FS is behind the DB
     * AHEAD indicates the FS is ahead of the DB
     */
    public enum RestoreMode {
        BEHIND,
        AHEAD
    }

    private static final Logger log = LoggerFactory.getLogger(RestoreIgnoreRule.class);
    private static final String RESTORE_MODE_PROPERTY = "restore.mode";
    private final RestoreMode restoreMode;

    public RestoreIgnoreRule() {
        this.restoreMode = getRestoreMode();
        switch (restoreMode) {
            case BEHIND:
                log.info("Tests annotated with @Behind will be executed");
                break;
            case AHEAD:
                log.info("Tests annotated with @Ahead will be executed");
                break;
        }
    }

    @Override
    public Statement apply(Statement base, Description description) {
        switch (restoreMode) {
            case BEHIND:
                if (isAhead(description)) {
                    return new SkipTest(getSkipReason(description));
                }
                break;
            case AHEAD:
                if (isBehind(description)) {
                    return new SkipTest(getSkipReason(description));
                }
                break;
        }
        return base;
    }

    private static RestoreMode getRestoreMode() {
        String restoreModeProperty = System.getProperty(RESTORE_MODE_PROPERTY);
        if (restoreModeProperty == null) {
            throw new AssertionError("The environmental variable \"" + RESTORE_MODE_PROPERTY +
                    "\" must be set. Please specify -D" + RESTORE_MODE_PROPERTY + "=<value> " +
                    "where <value> must be: " + Joiner.on(" or ").join(EnumSet.allOf(RestoreMode.class)));
        }
        restoreModeProperty = restoreModeProperty.toUpperCase();
        try {
            return RestoreMode.valueOf(restoreModeProperty);
        } catch (IllegalArgumentException e) {
            throw new AssertionError("The environmental variable \"" + RESTORE_MODE_PROPERTY +
                    "\" contains an invalid value: \"" + restoreModeProperty + "\". Please specify -D" +
                    RESTORE_MODE_PROPERTY + "=<value> " + "where <value> must be: " +
                    Joiner.on(" or ").join(EnumSet.allOf(RestoreMode.class)));
        }
    }

    private String getSkipReason(Description description) {
        String moreDetail;
        switch (restoreMode) {
            case BEHIND:
                moreDetail = "Not annotated with @Behind";
                break;
            case AHEAD:
                moreDetail = "Not annotated with @Ahead";
                break;
            default:
                moreDetail = "Unknown reason";
        }

        return MessageFormat.format("{0}.{1}: Skipped: {2}",
                description.getTestClass().getSimpleName(), description.getMethodName(),
                moreDetail);
    }

    private static boolean isBehind(Description description) {
        return description.getAnnotation(Behind.class) != null;
    }

    private static boolean isAhead(Description description) {
        return description.getAnnotation(Ahead.class) != null;
    }

    private static class SkipTest extends Statement {

        private final String skipReason;

        SkipTest(String skipReason) {
            this.skipReason = skipReason;
        }

        @Override
        public void evaluate() {
            Assume.assumeTrue(skipReason, false);
        }
    }
}
