package org.jboss.as.test.integration.deployment.structure.war.symbolicLink;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.servlet.jsp.SkipPageException;

import org.junit.Assert;
import org.junit.Assume;
import org.apache.commons.lang.SystemUtils;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.test.shared.TestSuiteEnvironment;
import org.jboss.dmr.ModelNode;
import org.jboss.logging.Logger;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;


/**
 * Unit test to make sure that the symbolic linking feature on the web subsystem is enabled properly.
 * Corresponding JIRA: AS7-3414.
 *
 * @author navssurtani
 */

@RunWith(Arquillian.class)
@RunAsClient
public class SymlinkingUnitTestCase {

    private static final Logger logger = Logger.getLogger(SymlinkingUnitTestCase.class);
    private static final String WAR_NAME = "explodedDeployment.war";

    private static File warDeployment = null;
    private static File symbolic = null;
    private static ModelControllerClient controllerClient = TestSuiteEnvironment.getModelControllerClient();


    @BeforeClass
    public static void beforeClass() throws IOException, InterruptedException {
        Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS);
        logger.infof("beforeClass() call.");
        // We are going to check whether or not the war deployment actually exists.
        Assert.assertTrue(checkForDeployment());
        // Now create the symlink.
        createSymlink();
    }

    @AfterClass
    public static void afterClass() throws IOException {
        if (SystemUtils.IS_OS_WINDOWS) {
            // skip windows;
        } else {
            logger.infof("afterClass() call.");
            // Delete the symlink
            symbolic.delete();
            controllerClient.close();
            controllerClient = null;
        }
    }

    @After
    public void tearDown() throws IOException {
        logger.infof("In the tearDown() call.");

        ModelNode removeSystemProperty = Util.createRemoveOperation(PathAddress.pathAddress(systemPropertyAddress()));

        // Define the content.
        ModelNode content = new ModelNode();
        content.get(0).get(ModelDescriptionConstants.ARCHIVE).set(false);
        content.get(0).get(ModelDescriptionConstants.PATH).set(warDeployment.getAbsolutePath());

        // Undeploy operation.
        ModelNode undeployOperation = Util.getEmptyOperation(ModelDescriptionConstants.UNDEPLOY, new ModelNode().add(ModelDescriptionConstants.DEPLOYMENT, WAR_NAME));
        undeployOperation.get(ModelDescriptionConstants.CONTENT).set(ModelDescriptionConstants.CONTENT);
        undeployOperation.get(ModelDescriptionConstants.ENABLE).set(true);

        // Remove operation.
        ModelNode removeOperation = Util.getEmptyOperation(ModelDescriptionConstants.REMOVE, new ModelNode().add(ModelDescriptionConstants.DEPLOYMENT, WAR_NAME));
        removeOperation.get(ModelDescriptionConstants.CONTENT).set(ModelDescriptionConstants.CONTENT);
        removeOperation.get(ModelDescriptionConstants.ENABLE).set(true);

        // Composite operation.
        ModelNode compositeOperation = Util.getEmptyOperation(ModelDescriptionConstants.COMPOSITE, new ModelNode());
        ModelNode steps = compositeOperation.get(ModelDescriptionConstants.STEPS);
        steps.add(undeployOperation);
        steps.add(removeOperation);
        steps.add(removeSystemProperty);

        //logger.infof("Composite operation: %s", compositeOperation.toString());
        ModelNode compositeResult = controllerClient.execute(compositeOperation);

        Assert.assertEquals(ModelDescriptionConstants.SUCCESS, compositeResult.get(ModelDescriptionConstants.OUTCOME).asString());

    }

    @Test
    public void testEnabled() throws IOException {
        //logger.infof("Testing enabled bit");
        // By default we should not be able to browse to the symlinked page.
        Assert.assertTrue(getURLcode("symbolic") == 404);
        setup(true);
        // First make sure that we can browse to index.html. This should work in the enabled or disabled version.
        Assert.assertTrue(getURLcode("index") == 200);
        // Now with symbolic.html.
        Assert.assertTrue(getURLcode("symbolic") == 200);
    }

    @Test
    public void testDisabled() throws IOException {
        logger.infof("Testing disabled bit.");
        // By default we should not be able to browse to the symlinked page.
        Assert.assertTrue(getURLcode("symbolic") == 404);
        setup(false);
        // First make sure that we can browse to index.html. This should work in the enabled or disabled version.
        Assert.assertTrue(getURLcode("index") == 200);
        // Now with symbolic.html.
        Assert.assertTrue(getURLcode("symbolic") == 404);
    }

    private void setup(boolean symlinkingEnabled) throws IOException {
        logger.infof("Entered the setup call within %s & the boolean parameter is %s",
                this.getClass().getName(), symlinkingEnabled);
        ModelNode addSysProperty = Util.createAddOperation(PathAddress.pathAddress(systemPropertyAddress()));
        addSysProperty.get(ModelDescriptionConstants.VALUE).set(Boolean.toString(symlinkingEnabled));
        ModelNode result = controllerClient.execute(addSysProperty);
        Assert.assertEquals(ModelDescriptionConstants.SUCCESS, result.get(ModelDescriptionConstants.OUTCOME).asString());

        // Define the content.
        ModelNode content = new ModelNode();
        content.get(0).get(ModelDescriptionConstants.ARCHIVE).set(false);
        content.get(0).get(ModelDescriptionConstants.PATH).set(warDeployment.getAbsolutePath());

        // Add operation.
        ModelNode addOperation = Util.getEmptyOperation(ModelDescriptionConstants.ADD, new ModelNode().add(ModelDescriptionConstants.DEPLOYMENT, WAR_NAME));
        addOperation.get(ModelDescriptionConstants.CONTENT).set(content);
        addOperation.get(ModelDescriptionConstants.ENABLE).set(true);
        result = controllerClient.execute(addOperation);
        Assert.assertEquals(ModelDescriptionConstants.SUCCESS, result.get(ModelDescriptionConstants.OUTCOME).asString());

        // Deployment operation.
        ModelNode deploymentOperation = Util.getEmptyOperation(ModelDescriptionConstants.DEPLOY, new ModelNode().add(ModelDescriptionConstants.DEPLOYMENT, WAR_NAME));
        deploymentOperation.get(ModelDescriptionConstants.CONTENT).set(content);
        deploymentOperation.get(ModelDescriptionConstants.ENABLE).set(true);
        result = controllerClient.execute(deploymentOperation);
        Assert.assertEquals(ModelDescriptionConstants.SUCCESS, result.get(ModelDescriptionConstants.OUTCOME).asString());
    }

    private int getURLcode(String htmlPage) {
        int code = 0;
        try {
            String url = "http://" + TestSuiteEnvironment.getServerAddress() + ":8080/explodedDeployment/"
                    + htmlPage + ".html";
            logger.infof("%s is the built URL.", url);
            HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.connect();
            code = urlConnection.getResponseCode();
            logger.infof("Received response code of: " + code);
        } catch (Exception e) {
            logger.infof("Exception of type: %s caught", e.getClass(), e);
        }
        return code;
    }

    private ModelNode systemPropertyAddress() {
        ModelNode address = new ModelNode();
        address.add("system-property", "symbolic-linking-enabled");
        address.protect();
        return address;
    }

    private static boolean checkForDeployment() {
        String warLocation = getWarLocation();
        warDeployment = new File(warLocation);
        logger.infof("Checking to see if exploded deployment exists at path: " + warDeployment.getAbsolutePath());
        return warDeployment.exists();
    }

    private static String getWarLocation() {
        return SymlinkingUnitTestCase.class.getResource(WAR_NAME).getPath();
    }

    private static void createSymlink() throws IOException, InterruptedException {
        // TODO: navssurtani: once AS7 is on a minimum of Java 7 then we can change the approach used to create the symlink.
        File index = new File(warDeployment, "index.html");
        logger.infof("Path to index file is: " + index.getAbsolutePath());
        Assert.assertTrue(index.exists());
        // Now set up the information to create the symlink.
        String toExecute;

        if (SystemUtils.IS_OS_WINDOWS) {
            logger.infof("Windows based OS detected.");
            toExecute = "mklink \\D " + index.getAbsolutePath() + " " + warDeployment.getAbsolutePath()
                    + "\\symbolic.html";
        } else {
            logger.infof("UNIX based OS detected.");
            toExecute = "ln -s " + index.getAbsolutePath() + " " + warDeployment.getAbsolutePath() + "/symbolic.html";
        }

        logger.infof("String to be executed is: " + toExecute);

        Runtime.getRuntime().exec(toExecute).waitFor();
        symbolic = new File(warDeployment.getAbsolutePath(), "symbolic.html");


        logger.infof("Absolute path of symbolic file is located at %s. Checking to see if the file really exists.",
                symbolic.getAbsolutePath());
        Assert.assertTrue(symbolic.exists());
    }
}
