package io.qameta.allure.junit5;

import io.qameta.allure.Allure;
import io.qameta.allure.AllureLifecycle;
import io.qameta.allure.model.Stage;
import io.qameta.allure.model.Status;
import io.qameta.allure.model.TestResult;
import io.qameta.allure.util.ResultsUtils;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestIdentifier;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

import static io.qameta.allure.model.Status.FAILED;
import static io.qameta.allure.model.Status.PASSED;
import static io.qameta.allure.model.Status.SKIPPED;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * @author ehborisov
 */
public class AllureJunit5 implements TestExecutionListener {

    private final ThreadLocal<String> tests
            = InheritableThreadLocal.withInitial(() -> UUID.randomUUID().toString());

    private final AllureLifecycle lifecycle;

    public AllureJunit5(final AllureLifecycle lifecycle) {
        this.lifecycle = lifecycle;
    }

    public AllureJunit5() {
        this.lifecycle = Allure.getLifecycle();
    }

    public AllureLifecycle getLifecycle() {
        return lifecycle;
    }

    @Override
    public void executionStarted(final TestIdentifier testIdentifier) {
        if (testIdentifier.isTest()) {
            final String uuid = tests.get();
            final TestResult result = new TestResult()
                    .withUuid(uuid)
                    .withName(testIdentifier.getDisplayName())
                    .withHistoryId(getHistoryId(testIdentifier))
                    .withStage(Stage.RUNNING);
            getLifecycle().scheduleTestCase(result);
            getLifecycle().startTestCase(uuid);
        }
    }

    @Override
    public void executionFinished(final TestIdentifier testIdentifier, final TestExecutionResult testExecutionResult) {
        if (testIdentifier.isTest()) {
            final String uuid = tests.get();
            tests.remove();
            getLifecycle().updateTestCase(uuid, result -> {
                result.setStage(Stage.FINISHED);
                switch (testExecutionResult.getStatus()) {
                    case FAILED:
                        testExecutionResult.getThrowable().ifPresent(throwable -> {
                            result.setStatus(getStatus(throwable));
                            result.setStatusDetails(ResultsUtils.getStatusDetails(throwable).orElse(null));
                        });
                        break;
                    case SUCCESSFUL:
                        result.setStatus(PASSED);
                        break;
                    default:
                        result.setStatus(SKIPPED);
                        testExecutionResult.getThrowable().ifPresent(throwable ->
                                result.setStatusDetails(ResultsUtils.getStatusDetails(throwable).orElse(null))
                        );
                        break;
                }
            });
            getLifecycle().stopTestCase(uuid);
            getLifecycle().writeTestCase(uuid);
        }
    }

    protected Status getStatus(final Throwable throwable) {
        return ResultsUtils.getStatus(throwable).orElse(FAILED);
    }

    protected String getHistoryId(final TestIdentifier testIdentifier) {
        return md5(testIdentifier.getUniqueId());
    }

    private String md5(final String source) {
        final byte[] bytes = getMessageDigest().digest(source.getBytes(UTF_8));
        return new BigInteger(1, bytes).toString(16);
    }

    private MessageDigest getMessageDigest() {
        try {
            return MessageDigest.getInstance("md5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("Could not find md5 hashing algorithm", e);
        }
    }
}

@@ -100,16 +100,53 @@
 ecycle;%0A
+import io.qameta.allure.model.Label;%0A
 import i
@@ -174,16 +174,16 @@
 .Stage;%0A
-
 import i
@@ -347,24 +347,90 @@
 tionResult;%0A
+import org.junit.platform.engine.support.descriptor.MethodSource;%0A
 import org.j
@@ -639,16 +639,43 @@
 eption;%0A
+import java.util.Optional;%0A
 import j
@@ -1550,32 +1550,227 @@
 ier.isTest()) %7B%0A
+            final Optional%3CMethodSource%3E methodSource = testIdentifier.getSource()%0A                    .filter(MethodSource.class::isInstance)%0A                    .map(MethodSource.class::cast);%0A
             fina
@@ -2017,16 +2017,16 @@
 ifier))%0A
-
         
@@ -2064,16 +2064,291 @@
 NNING);%0A
+%0A            methodSource.ifPresent(source -%3E %7B%0A                result.getLabels().add(new Label().withName(%22suite%22).withValue(source.getClassName()));%0A                result.getLabels().add(new Label().withName(%22package%22).withValue(source.getClassName()));%0A            %7D);%0A%0A
         

add suite and package for junit 5 tests (via #94)