package test.testng1396;

import org.testng.IMethodInstance;
import org.testng.IMethodInterceptor;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import org.testng.TestNG;
import org.testng.annotations.Test;
import org.testng.collections.Lists;
import org.testng.xml.XmlSuite;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import static org.testng.Assert.fail;

public class ParallelByInstancesInterceptorTest {

    @Test(description = "test for https://github.com/cbeust/testng/issues/1396")
    public void should_honor_interceptor_order_when_running_parallel_instances() {
        TestNG tng = new TestNG();
        tng.setVerbose(0);

        // 10 instances is enough that we see the non-deterministic ordering of hashmaps more often than not
        tng.setTestClasses(new Class[]{
                SampleFor1396.Sample9For1396.class,
                SampleFor1396.Sample8For1396.class,
                SampleFor1396.Sample7For1396.class,
                SampleFor1396.Sample6For1396.class,
                SampleFor1396.Sample5For1396.class,
                SampleFor1396.Sample4For1396.class,
                SampleFor1396.Sample3For1396.class,
                SampleFor1396.Sample2For1396.class,
                SampleFor1396.Sample1For1396.class,
                SampleFor1396.Sample0For1396.class
        });

        // bug only exists when running parallel by instances with this flag set to false
        tng.setParallel(XmlSuite.ParallelMode.INSTANCES);
        tng.setPreserveOrder(false);
        // thread count of 1 forces sequential execution so we can verify ordering
        tng.setThreadCount(1);

        TestListenerAdapter adapter = new TestListenerAdapter();
        tng.addListener(adapter);

        // Runs tests annotated with @TestNG1396HighPriority before tests without it
        ReverseOrderTestInterceptor listener = new ReverseOrderTestInterceptor();
        tng.addListener(listener);

        tng.run();

        List<ITestResult> passedTests = adapter.getPassedTests();

        boolean lowPrioritySeen = false;

        for (ITestResult testResult : passedTests) {
            if (isHighPriority(testResult.getMethod())) {
                if (lowPrioritySeen) {
                    fail("high priority should be first");
                }
            } else {
                lowPrioritySeen = true;
            }
        }
    }

    private static boolean isHighPriority(IMethodInstance instance) {
        return instance.getInstance().getClass().getAnnotation(TestNG1396HighPriority.class) != null;
    }

    private static boolean isHighPriority(ITestNGMethod method) {
        return method.getInstance().getClass().getAnnotation(TestNG1396HighPriority.class) != null;
    }

    public class ReverseOrderTestInterceptor implements IMethodInterceptor {
        @Override
        public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) {
            List<IMethodInstance> sorted = Lists.newArrayList(methods);
            Collections.sort(sorted, new PriorityComparator());
            return sorted;
        }
    }

    public class PriorityComparator implements Comparator<IMethodInstance> {
        @Override
        public int compare(IMethodInstance o1, IMethodInstance o2) {
            if (isHighPriority(o1)) {
                if (isHighPriority(o2)) {
                    return compareMethodNames(o1, o2);
                }
                return -1;
            }
            if (isHighPriority(o2)) {
                return 1;
            }
            return compareMethodNames(o1, o2);
        }

        private int compareMethodNames(IMethodInstance o1, IMethodInstance o2) {
            return o2.getMethod().getQualifiedName().compareTo(o1.getMethod().getQualifiedName());
        }
    }
}
