package it.com.atlassian.stash;

import com.atlassian.qunit.test.runner.QUnitIndexPage;
import com.atlassian.qunit.test.runner.QUnitTestMeta;
import com.atlassian.qunit.test.runner.QUnitTestPage;
import com.atlassian.qunit.test.runner.QUnitTestResult;
import com.atlassian.bitbucket.test.annotations.CompatibleWithProductTopologies;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import it.com.atlassian.stash.rules.IgnoreJavaScriptErrors;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static com.atlassian.bitbucket.test.ProductTopology.BITBUCKET_STANDALONE;
import static org.junit.Assert.assertTrue;

@CompatibleWithProductTopologies(BITBUCKET_STANDALONE)
@IgnoreJavaScriptErrors("Qunit handles JavaScript errors itself")
public final class QUnitTest extends BaseBrowserFuncTest {

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

    private static final String OUTPUT_LOCATION = "stash.qunit.testoutput.location";
    private static final String INCLUDES = "stash.qunit.test.includes";
    private static final String EXCLUDES = "stash.qunit.test.excludes";

    private final File testResultsFile;
    private final Set<String> includes;
    private final Set<String> excludes;

    public QUnitTest() {
        String location = System.getProperty(OUTPUT_LOCATION);
        String tmpDir = System.getProperty("java.io.tmpdir");
        if (!StringUtils.isEmpty(location)) {
            // create location if it does not exist
            File locationFile = new File(location);

            if (!locationFile.exists()) {
                log.debug("{} does not exist, creating", location);
                System.out.println(locationFile + " does not exist, creating " + locationFile.getAbsolutePath());
                if (!locationFile.mkdirs()) {
                    log.error("Could not create {}, falling back to {}", location, OUTPUT_LOCATION);
                    System.out.println("Could not create " + locationFile + " falling back to " + tmpDir);
                    location = tmpDir;
                }
            }
        } else {
            location = tmpDir;
            log.error("Writing result XML to {}, {} not defined", location, OUTPUT_LOCATION);
        }

        log.info("location = {}", location);

        testResultsFile = new File(location);

        includes = getPluginKeysFromProperty(INCLUDES);
        excludes = getPluginKeysFromProperty(EXCLUDES);
    }

    private Set<String> getPluginKeysFromProperty(String propertyName) {
        String pluginKeysAsString = System.getProperty(propertyName);
        return StringUtils.isNotBlank(pluginKeysAsString) ?
                ImmutableSet.copyOf(pluginKeysAsString.trim().split("/")) :
                Collections.<String>emptySet();
    }

    @Test
    public void testQUnitTests() throws Exception {
        QUnitIndexPage indexPage = BITBUCKET.visit(QUnitIndexPage.class);
        List<QUnitTestResult> failures = Lists.newArrayList();

        List<QUnitTestMeta> retryTests = Lists.newArrayList();

        for (QUnitTestMeta test : indexPage.getAllTests()) {
            String pluginKey = getPluginKey(test);
            if (isExcluded(pluginKey) || !isIncluded(pluginKey)) {
                log.info("Skipping {}", test.getSuiteName());
                continue;
            }

            QUnitTestResult result;
            try {
                result = runQunitTest(test);
            } catch (AssertionError e) {
                retryTests.add(test);
                continue;
            }
            collectResults(failures, result);
        }

        // CI seems to sometimes fail and hang while trying to get the results
        // Retry the tests once if the test run failed to complete
        for (QUnitTestMeta test : retryTests) {
            log.info("Retrying {}", test.getSuiteName());
            QUnitTestResult result = runQunitTest(test);
            collectResults(failures, result);
        }


        List<String> failureMessages = Lists.newArrayList();
        for (QUnitTestResult failure : failures) {
            for (QUnitTestResult.TestResult result : failure.getResults()) {
                if (result.failed > 0) {
                    failureMessages.add(result.module + " -- " + result.name);
                    failureMessages.add(result.getFullLogMessage());
                }
            }
        }
        assertTrue("QUnit failures:\n" + StringUtils.join(failureMessages, '\n'), failures.isEmpty());
    }

    private void collectResults(List<QUnitTestResult> failures, QUnitTestResult result) throws Exception {
        if (result.getFailures() > 0) {
            failures.add(result);
        }
        result.write(testResultsFile);
    }

    private QUnitTestResult runQunitTest(QUnitTestMeta test) throws Exception {
        QUnitTestPage testPage = BITBUCKET.visit(QUnitTestPage.class, test);
        return testPage.getResult();
    }

    private String getPluginKey(QUnitTestMeta meta) {
        String path = meta.getQUnitTestFilePath();
        return path.substring(0,  path.indexOf('/'));
    }

    private boolean isExcluded(String pluginKey) {
        return excludes.contains(pluginKey);
    }

    private boolean isIncluded(String pluginKey) {
        return includes.isEmpty() || includes.contains(pluginKey);
    }

}
