package org.jboss.as.test.shared.util;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.function.Supplier;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assume;

/**
 * Helper methods which help to skip tests for feature which is not yet fully working. Put the call of the method directly into
 * the failing test method, or if you want to skip whole test class, then put the method call into method annotated with
 * {@link org.junit.BeforeClass}.
 *
 * @author Josef Cacek
 */
public class AssumeTestGroupUtil {

    /**
     * An empty deployment archive that can be returned from an @Deployment method in the normal deployment
     * returned from the method cannot be successfully deployed under certain conditions. Arquillian will deploy
     * managed deployments <strong>before executing any @BeforeClass method</strong>, so if your @BeforeClass
     * method conditionally disables running the tests with an AssumptionViolatedException, the deployment will
     * still get deployed and may fail the test. So have it deploy this instead so your @BeforeClass gets called.
     */
    public static final JavaArchive EMPTY_JAR = emptyJar("empty");
    /** Same as {@link #EMPTY_JAR} but is a {@link WebArchive}. */
    public static final WebArchive EMPTY_WAR = emptyWar("empty");
    /** Same as {@link #EMPTY_JAR} but is an {@link EnterpriseArchive}. */
    public static final EnterpriseArchive EMPTY_EAR = emptyEar("empty");

    public static JavaArchive emptyJar(String name) {
        String jarName = name.endsWith(".jar") ? name : name + ".jar";
        return ShrinkWrap.create(JavaArchive.class, jarName)
                .addManifest();
    }
    public static WebArchive emptyWar(String name) {
        String warName = name.endsWith(".war") ? name : name + ".war";
        return ShrinkWrap.create(WebArchive.class, warName)
                .addManifest();
    }
    public static EnterpriseArchive emptyEar(String name) {
        String earName = name.endsWith(".ear") ? name : name + ".ear";
        return ShrinkWrap.create(EnterpriseArchive.class, earName)
                .addManifest();
    }

    /**
     * Assume for test failures when running with Elytron profile enabled. It skips test in case the {@code '-Delytron'} Maven
     * argument is used (for Elytron profile activation). For legacy-security only tests.
     * <p>
     * <strong>Note that this method is misnamed given its actual function; an appropriate name would be
     * assumeElytronProfileDisabled, since -Delytron being present is what violates the assumption.</strong>
     * But we leave the name and the behavior as is to avoid breaking any external test code that
     * uses this, and we don't add a new method with the right name as that would even more strongly imply
     * that this method functions differently than it does.
     */
    public static void assumeElytronProfileEnabled() {
        assumeCondition("Tests failing in Elytron profile are disabled", () -> System.getProperty("elytron") == null);
    }

    /**
     * Assume for test failures when running with Elytron profile enabled. It skips test in case the {@code '-Delytron'} Maven
     * argument is used (for Elytron profile activation). For legacy-security only tests.
     */
    public static boolean isElytronProfileEnabled() {
        return System.getProperty("elytron") != null;
    }

    /**
     * Assume for tests that fail when the security manager is enabled. This should be used sparingly and issues should
     * be filed for failing tests so a proper fix can be done.
     * <p>
     * Note that this checks the {@code security.manager} system property and <strong>not</strong> that the
     * {@link System#getSecurityManager()} is {@code null}. The property is checked so that the assumption check can be
     * done in a {@link org.junit.Before @Before} or {@link org.junit.BeforeClass @BeforeClass} method.
     * </p>
     */
    public static void assumeSecurityManagerDisabled() {
        assumeCondition("Tests failing if the security manager is enabled.", () -> System.getProperty("security.manager") == null);
    }

    public static void assumeSecurityManagerDisabledOrAssumeJDKVersionBefore(int javaSpecificationVersion) {
        assumeCondition("Tests failing if the security manager is enabled and JDK in use is after " + javaSpecificationVersion + ".",
                () -> (System.getProperty("security.manager") == null || getJavaSpecificationVersion() < javaSpecificationVersion));
    }

    /**
     * Assume for tests that fail when the JVM version is too low. This should be used sparingly.
     *
     * @param javaSpecificationVersion the JDK specification version. Use 8 for JDK 8. Must be 8 or higher.
     */
    public static void assumeJDKVersionAfter(int javaSpecificationVersion) {
        assert javaSpecificationVersion >= 8; // we only support 8 or later so no reason to call this for 8
        assumeCondition("Tests failing if the JDK in use is before " + javaSpecificationVersion + ".",
                () -> isJDKVersionAfter(javaSpecificationVersion));
    }

    /**
     * Assume for tests that fail when the JVM version is too high. This should be used sparingly.
     *
     * @param javaSpecificationVersion the JDK specification version. Must be 9 or higher.
     */
    public static void assumeJDKVersionBefore(int javaSpecificationVersion) {
        assert javaSpecificationVersion >= 9; // we only support 8 or later so no reason to call this for 8
        assumeCondition("Tests failing if the JDK in use is after " + javaSpecificationVersion + ".",
                () -> isJDKVersionBefore(javaSpecificationVersion));
    }

    /**
     * Check if the current JDK specification version is greater than the given value.
     *
     * @param javaSpecificationVersion the JDK specification version. Use 8 for JDK 8.
     */
    public static boolean isJDKVersionAfter(int javaSpecificationVersion) {
        return getJavaSpecificationVersion() > javaSpecificationVersion;
    }

    /**
     * Check if the current JDK specification version is less than the given value.
     *
     * @param javaSpecificationVersion the JDK specification version. Use 8 for JDK 8.
     */
    public static boolean isJDKVersionBefore(int javaSpecificationVersion) {
        return getJavaSpecificationVersion() < javaSpecificationVersion;
    }
    // BES 2020/05/18 I added this along with assumeJDKVersionAfter/assumeJDKVersionBefore but commented it
    // out because using it seems like bad practice. If there's a legit need some day, well, here's the code...
//    /**
//     * Assume for tests that fail when the JVM version is something. This should be used sparingly and issues should
//     * be filed for failing tests so a proper fix can be done, as it's inappropriate to limit a test to a single version.
//     *
//     * @param javaSpecificationVersion the JDK specification version. Use 8 for JDK 8. Must be 8 or higher.
//     */
//    public static void assumeJDKVersionEquals(int javaSpecificationVersion) {
//        assert javaSpecificationVersion >= 8; // we only support 8 or later
//        assumeCondition("Tests failing if the JDK in use is other than " + javaSpecificationVersion + ".",
//                () -> getJavaSpecificationVersion() == javaSpecificationVersion);
//    }

    private static int getJavaSpecificationVersion() {
        String versionString = System.getProperty("java.specification.version");
        versionString = versionString.startsWith("1.") ? versionString.substring(2) : versionString;
        return Integer.parseInt(versionString);
    }

    private static void assumeCondition(final String message, final Supplier<Boolean> assumeTrueCondition) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            @Override
            public Void run() {
                Assume.assumeTrue(message, assumeTrueCondition.get());
                return null;
            }
        });
    }

}
