/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2016, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.test.manualmode.deployment;

import static org.jboss.as.test.integration.management.util.ModelUtil.createOpNode;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.jboss.arquillian.container.test.api.ContainerController;
import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.junit.InSequence;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.test.shared.TestSuiteEnvironment;
import org.jboss.dmr.ModelNode;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * https://bugzilla.redhat.com/show_bug.cgi?id=997583
 * https://bugzilla.redhat.com/show_bug.cgi?id=1291710
 *
 * @author Tomas Hofman (thofman@redhat.com)
 */
@RunWith(Arquillian.class)
@RunAsClient
public class DeploymentScannerTestCase {

    private static final String CONTAINER = "default-jbossas";
    private static final String JBOSS_HOME = System.getProperty("jboss.home");
    private static final File DEPLOYMENT_DIR = new File(JBOSS_HOME + File.separator + "standalone" + File.separator + "deployments");

    private static final String FS_DEPLOYMENT_NAME = "fileSystemDeployment.war";
    private static final String FS_DEPLOYMENT_NAME2 = "fileSystemDeployment2.war";
    private static final String PERSISTENT_DEPLOYMENT_NAME = "persistentDeployment.war";

    @ArquillianResource
    private static ContainerController containerController;

    @ArquillianResource
    private static Deployer deployer;

    @Deployment(name = PERSISTENT_DEPLOYMENT_NAME, managed = false)
    public static Archive<?> createValidDeployment() {
        final WebArchive archive = ShrinkWrap.create(WebArchive.class, PERSISTENT_DEPLOYMENT_NAME);
        archive.setWebXML(DeploymentScannerRedeploymentTestCase.class.getPackage(), "web.xml");
        return archive;
    }

    private static Archive<?> createBrokenDeployment() {
        final WebArchive archive = ShrinkWrap.create(WebArchive.class);
        archive.addAsWebInfResource(new StringAsset("Malformed web.xml"), "web.xml");
        return archive;
    }

    @Before
    public void before() throws Exception {
        FileUtils.cleanDirectory(DEPLOYMENT_DIR);
    }

    @After
    public void after() throws Exception {
        if (!containerController.isStarted(CONTAINER)) {
            containerController.start(CONTAINER);
        }
        removeDeployments();
        containerController.stop(CONTAINER);
        FileUtils.cleanDirectory(DEPLOYMENT_DIR);
    }

    /**
     * https://bugzilla.redhat.com/show_bug.cgi?id=1291710
     *
     * When FS deployment fails during boot, persistent deployments are removed too.
     *
     * @throws Exception
     */
    @Test
    @InSequence(1)
    public void testFailedDeploymentWithPersistentDeployment() throws Exception {
        containerController.start(CONTAINER);
        removeDeployments();
        checkDeployed(/* empty */);

        deployer.deploy(PERSISTENT_DEPLOYMENT_NAME); // deploy working deployment
        containerController.stop(CONTAINER);
        Archive broken = createBrokenDeployment();
        exportArchiveToDeploymentFolder(broken, FS_DEPLOYMENT_NAME); // place broken war in deployments folder
        containerController.start(CONTAINER);

        Thread.sleep(2000);

        checkFailedMarkerCreated(FS_DEPLOYMENT_NAME);
        checkDeployed(PERSISTENT_DEPLOYMENT_NAME); // check that working deployment is still deployed
    }

    /**
     * https://bugzilla.redhat.com/show_bug.cgi?id=997583
     *
     * FS deployments that fail during boot are not removed.
     *
     * @throws Exception
     */
    @Test
    @InSequence(2)
    public void testFailedDeploymentDuringBoot() throws Exception {
        Archive broken = createBrokenDeployment();
        exportArchiveToDeploymentFolder(broken, FS_DEPLOYMENT_NAME); // place broken war in deployments folder
        containerController.start(CONTAINER);
        Thread.sleep(2000);

        checkFailedMarkerCreated(FS_DEPLOYMENT_NAME);
        checkDeployed(/* empty */);
    }

    @Test
    @InSequence(3)
    public void testFailedDeploymentWithCorrectDeploymentDuringBoot() throws Exception {
        // place broken war in deployments folder
        Archive broken = createBrokenDeployment();
        exportArchiveToDeploymentFolder(broken, FS_DEPLOYMENT_NAME);

        // place correct war in deployments folder
        Archive valid = createValidDeployment();
        exportArchiveToDeploymentFolder(valid, FS_DEPLOYMENT_NAME2);

        containerController.start(CONTAINER);
        Thread.sleep(2000);

        checkFailedMarkerCreated(FS_DEPLOYMENT_NAME);
        checkDeployed(FS_DEPLOYMENT_NAME2);
    }

    @Test
    @InSequence(4)
    public void testFailedDeploymentWhenRunning() throws Exception {
        containerController.start(CONTAINER); // start the server first
        checkDeployed(/* empty */);

        Archive broken = createBrokenDeployment();
        exportArchiveToDeploymentFolder(broken, FS_DEPLOYMENT_NAME); // place broken war in deployments folder
        Thread.sleep(6000);

        checkFailedMarkerCreated(FS_DEPLOYMENT_NAME);
        checkDeployed(/* empty */);
    }

    private static void exportArchiveToDeploymentFolder(Archive<?> archive, String fileName) {
        archive.as(ZipExporter.class).exportTo(new File(DEPLOYMENT_DIR, fileName), true);
    }

    private static void checkFailedMarkerCreated(String deployment) {
        Assert.assertTrue(String.format("Failed marker was not created. Deployment dir content is:\n%s",
                StringUtils.join(DEPLOYMENT_DIR.list(), "\n")),
                new File(DEPLOYMENT_DIR, deployment + ".failed").exists());
    }

    private static void checkDeployed(String... expected) throws Exception {
        List<String> deployments = listDeployments();
        if (!listEquals(Arrays.asList(expected), deployments)) {
            Assert.fail(String.format("Deployments don't match. Expected: <%s>, actual: <%s>",
                    StringUtils.join(expected, ", "), StringUtils.join(deployments, ", ")));
        }
    }

    private static boolean listEquals(List<String> first, List<String> second) {
        if (first.size() != second.size()) {
            return false;
        }
        for (String value: first) {
            if (!second.contains(value)) {
                return false;
            }
        }
        return true;
    }

    private static List<String> listDeployments() throws Exception {
        final ModelControllerClient client = TestSuiteEnvironment.getModelControllerClient();
        ModelNode operation = createOpNode("deployment=*", ModelDescriptionConstants.READ_RESOURCE_OPERATION);
        operation.get("recursive").set(false);
        ModelNode result = client.execute(operation);
        List<ModelNode> deployments = result.get("result").asList();
        List<String> deploymentNames = new ArrayList<String>();
        for (ModelNode deployment : deployments) {
            deploymentNames.add(deployment.get("result").get("name").asString());
        }
        return deploymentNames;
    }

    private static void removeDeployments() throws Exception {
        List<String> deployments = listDeployments();
        for (String deployment: deployments) {
            final ModelControllerClient client = TestSuiteEnvironment.getModelControllerClient();
            ModelNode operation = createOpNode("deployment=" + deployment, ModelDescriptionConstants.REMOVE);
            ModelNode result = client.execute(operation);
            Assert.assertEquals("Couldn't remove deployment " + deployment + ": " + result.asString(),
                    "success", result.get("outcome").asString());
        }
    }

}
