package it.com.atlassian.stash.rules;

import com.atlassian.webdriver.browsers.WebDriverBrowserAutoInstall;
import com.atlassian.webdriver.utils.JavaScriptUtils;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.openqa.selenium.WebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;

/**
 * A TestRule which captures any JavaScript errors which occurred in a test failure and adds them as a cause to the
 * exception chain
 */
public class JavaScriptErrorRule implements TestRule {

    private static final Logger log = LoggerFactory.getLogger(JavaScriptErrorRule.class);

    private final Supplier<? extends WebDriver> webDriver;

    public JavaScriptErrorRule() {
        this.webDriver = WebDriverBrowserAutoInstall.driverSupplier();
    }

    @Override
    public Statement apply(final Statement base, Description description) {
        if (description.getTestClass().isAnnotationPresent(IgnoreJavaScriptErrors.class) ||
                description.getAnnotation(IgnoreJavaScriptErrors.class) != null) {
            return base;
        }

        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    base.evaluate();
                } catch (Throwable e) {
                    maybeAddJavaScriptErrors(e);

                    throw e;
                }
            }
        };
    }

    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    private void maybeAddJavaScriptErrors(Throwable t) {
        Throwable rootCause = Throwables.getRootCause(t);

        if (rootCause instanceof JavaScriptException) {
            // A JavaScript error has already been added
            return;
        }

        ArrayList<String> errors = new ArrayList<>();
        try {
            final WebDriver driver = webDriver.get();
            Object result = JavaScriptUtils.execute("return window.collectedErrors || []", driver);
            if (result instanceof Collection) {
                @SuppressWarnings("unchecked")
                Collection<String> resultErrors = (Collection) result;
                errors.addAll(resultErrors);
            }
        } catch (Exception e) {
            log.debug("An error occurred while collecting JavaScript errors", e);
        }

        if (errors.isEmpty()) {
            return;
        }

        // Instead of adding the original exception as the cause, add the JavaScriptError as
        // the cause. This will lead to a more obvious error message
        String message = "The following JavaScript error(s) occurred: " +
                (errors.size() > 1 ? errors : errors.get(0));

        try {
            rootCause.initCause(new JavaScriptException(message));
        } catch (Exception e) {
            log.warn("An error occurred while setting JavaScript error " + message, e);
        }
    }

}
