package unittesting;

import java.lang.reflect.InvocationTargetException;
import java.security.AccessControlException;
import java.util.Date;

import org.apache.commons.lang3.StringUtils;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

import com.mendix.systemwideinterfaces.core.IContext;

import unittesting.proxies.TestSuite;
import unittesting.proxies.UnitTest;
import unittesting.proxies.UnitTestResult;

public class UnitTestRunListener extends RunListener {
	

	private IContext context;
	private TestSuite testSuite;

	public UnitTestRunListener(IContext context, TestSuite testSuite) {
		this.context = context;
		this.testSuite = testSuite;
	}

	@Override
	public void testRunStarted(Description description) throws java.lang.Exception {
		TestManager.LOG.info("Starting test run");
	}

	@Override
	public void testRunFinished(Result result) throws java.lang.Exception {
		TestManager.LOG.info("Test run finished");
	}

	@Override
	public void testStarted(Description description) throws java.lang.Exception {
		String message = "Starting JUnit test " + description.getClassName() + "." + description.getMethodName();
		TestManager.LOG.info(message);
		TestManager.instance().reportStep(message);
		
		UnitTest t = getUnitTest(description);
		t.setResult(UnitTestResult._1_Running);
		t.setResultMessage("");
		t.setLastRun(new Date());
		t.commit();
	}

	private UnitTest getUnitTest(Description description) {
		return TestManager.instance().getUnitTest(context, testSuite, description, false);
	}

	@Override
	public void testFinished(Description description) throws Exception {
		TestManager.LOG.info("Finished test " + description.getClassName() + "." + description.getMethodName());
		
		UnitTest t = getUnitTest(description);

		if (t.getResult() == UnitTestResult._1_Running) {
			t.setResult(UnitTestResult._3_Success);

			long delta = getUnitTestInnerTime(description, t);

			t.setResultMessage("JUnit test completed successfully");
			t.setReadableTime((delta > 10000 ? Math.round(delta / 1000) + " seconds" : delta + " milliseconds"));
		}
		
		t.setLastStep(TestManager.instance().getLastReportedStep());
		t.commit();
	}

	@Override
	public void testFailure(Failure failure) throws java.lang.Exception {
		boolean isCloudSecurityError = 
				failure.getException() != null && 
				failure.getException() instanceof AccessControlException &&
				((AccessControlException) failure.getException()).getPermission().getName().equals("accessDeclaredMembers");
		
		UnitTest t = getUnitTest(failure.getDescription());

		/** 
		 * Test itself failed
		 */		
		TestManager.LOG.error("Failed test (at step '" + TestManager.instance().getLastReportedStep() + "') " + failure.getDescription().getClassName() + "." + failure.getDescription().getMethodName() + " : " + failure.getMessage(), failure.getException());

		testSuite.setTestFailedCount(testSuite.getTestFailedCount() + 1);
		testSuite.commit();
		
		t.setResult(UnitTestResult._2_Failed);
		t.setResultMessage(String.format("%s %s: %s\n\n:%s",
				isCloudSecurityError ? "CLOUD SECURITY EXCEPTION \n\n" + TestManager.CLOUD_SECURITY_ERROR : "FAILED",
				findProperExceptionLine(failure.getTrace()),
				failure.getMessage(),
				failure.getTrace()
				));
		
		t.setLastStep(TestManager.instance().getLastReportedStep());
		t.setLastRun(new Date());
		t.commit();
	}

	private long getUnitTestInnerTime(Description description, UnitTest t)
			throws IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, NoSuchMethodException, SecurityException
			{
		long delta = System.currentTimeMillis() - t.getLastRun().getTime();

		if (AbstractUnitTest.class.isAssignableFrom(description.getTestClass())) 
			delta = (Long) description.getTestClass().getMethod("getTestRunTime").invoke(null);
		return delta;
	}

	private String findProperExceptionLine(String trace)
	{
		String[] lines = StringUtils.split(trace,"\n");
		if (lines.length > 2)
			for(int i = 1; i < lines.length; i++) {
				String line = lines[i].trim();
				if (!line.startsWith("at org.junit") && line.contains("(")) 
					return " at " + line.substring(line.indexOf('(') + 1, line.indexOf(')')).replace(":"," line ");
			}
		
		return "";
	}
}