/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.plugins.dependency.fromConfiguration;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.LegacySupport;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.dependency.AbstractDependencyMojoTestCase;
import org.apache.maven.plugins.dependency.testUtils.DependencyArtifactStubFactory;
import org.apache.maven.plugins.dependency.utils.markers.UnpackFileMarkerHandler;
import org.apache.maven.project.MavenProject;

public class TestUnpackMojo extends AbstractDependencyMojoTestCase {

    UnpackMojo mojo;

    protected void setUp() throws Exception {
        super.setUp("unpack", true, false);

        File testPom = new File(getBasedir(), "target/test-classes/unit/unpack-test/plugin-config.xml");
        mojo = (UnpackMojo) lookupMojo("unpack", testPom);
        mojo.setOutputDirectory(new File(this.testDir, "outputDirectory"));
        mojo.setMarkersDirectory(new File(this.testDir, "markers"));
        mojo.setSilent(true);

        assertNotNull(mojo);
        assertNotNull(mojo.getProject());
        // MavenProject project = mojo.getProject();
        // init classifier things
        // it needs to get the archivermanager
        stubFactory.setUnpackableFile(mojo.getArchiverManager());
        // i'm using one file repeatedly to archive so I can test the name
        // programmatically.
        stubFactory.setSrcFile(new File(
                getBasedir() + File.separatorChar + "target/test-classes/unit/unpack-dependencies-test/test.txt"));

        mojo.setUseJvmChmod(true);

        MavenSession session = newMavenSession(mojo.getProject());
        setVariableValueToObject(mojo, "session", session);

        LegacySupport legacySupport = lookup(LegacySupport.class);

        legacySupport.setSession(session);
        installLocalRepository(legacySupport);
    }

    public ArtifactItem getSingleArtifactItem(boolean removeVersion) throws MojoExecutionException {
        List<ArtifactItem> list = mojo.getProcessedArtifactItems(removeVersion);
        return list.get(0);
    }

    public void testGetArtifactItems() throws Exception {

        ArtifactItem item = new ArtifactItem();

        item.setArtifactId("artifact");
        item.setGroupId("groupId");
        item.setVersion("1.0");

        ArrayList<ArtifactItem> list = new ArrayList<>(1);
        list.add(createArtifact(item));

        mojo.setArtifactItems(list);

        ArtifactItem result = getSingleArtifactItem(false);
        assertEquals(mojo.getOutputDirectory(), result.getOutputDirectory());

        File output = new File(mojo.getOutputDirectory(), "override");
        item.setOutputDirectory(output);
        result = getSingleArtifactItem(false);
        assertEquals(output, result.getOutputDirectory());
    }

    public void assertMarkerFiles(Collection<ArtifactItem> items, boolean exist) {
        for (ArtifactItem item : items) {
            assertMarkerFile(exist, item);
        }
    }

    public void assertMarkerFile(boolean val, ArtifactItem item) {
        UnpackFileMarkerHandler handle = new UnpackFileMarkerHandler(item, mojo.getMarkersDirectory());
        try {
            assertEquals(val, handle.isMarkerSet());
        } catch (MojoExecutionException e) {
            fail(e.getLongMessage());
        }
    }

    public void testUnpackFile() throws Exception {
        List<ArtifactItem> list = stubFactory.getArtifactItems(stubFactory.getClassifiedArtifacts());

        mojo.setArtifactItems(list);

        mojo.execute();

        assertMarkerFiles(list, true);
    }

    public void testSkip() throws Exception {
        List<ArtifactItem> list = stubFactory.getArtifactItems(stubFactory.getClassifiedArtifacts());

        mojo.setSkip(true);
        mojo.setArtifactItems(list);

        mojo.execute();

        assertMarkerFiles(list, false);
    }

    public void testUnpackToLocation() throws Exception {
        List<ArtifactItem> list = stubFactory.getArtifactItems(stubFactory.getClassifiedArtifacts());
        ArtifactItem item = list.get(0);
        item.setOutputDirectory(new File(mojo.getOutputDirectory(), "testOverride"));

        mojo.setArtifactItems(list);

        mojo.execute();

        assertMarkerFiles(list, true);
    }

    public void testUnpackToLocationWhereLocationCannotBeCreatedThrowsException() throws Exception {
        List<ArtifactItem> list = stubFactory.getArtifactItems(stubFactory.getClassifiedArtifacts());
        ArtifactItem item = list.get(0);
        item.setOutputDirectory(new File(mojo.getOutputDirectory(), "testOverride"));

        mojo.setArtifactItems(list);
        final File currentFile = mojo.getOutputDirectory();

        // pretend that the output directory cannot be found event after mkdirs has been called by the mojo
        // ifor instance in the case when the outputDirectory cannot be created because of permissions on the
        // parent of the output directory
        mojo.setOutputDirectory(new File(currentFile.getAbsolutePath()) {

            private static final long serialVersionUID = -8559876942040177020L;

            @Override
            public boolean exists() {
                // this file will always report that it does not exist
                return false;
            }
        });
        try {
            mojo.execute();
            fail("Expected Exception Here.");
        } catch (MojoExecutionException e) {
            // caught the expected exception.
        }
    }

    public void testMissingVersionNotFound() throws Exception {
        ArtifactItem item = new ArtifactItem();

        item.setArtifactId("artifactId");
        item.setClassifier("");
        item.setGroupId("groupId");
        item.setType("type");

        List<ArtifactItem> list = new ArrayList<>();
        list.add(item);
        mojo.setArtifactItems(list);

        try {
            mojo.execute();
            fail("Expected Exception Here.");
        } catch (MojoExecutionException e) {
            // caught the expected exception.
        }
    }

    public List<Dependency> getDependencyList(ArtifactItem item) {
        Dependency dep = new Dependency();
        dep.setArtifactId(item.getArtifactId());
        dep.setClassifier(item.getClassifier());
        dep.setGroupId(item.getGroupId());
        dep.setType(item.getType());
        dep.setVersion("2.0-SNAPSHOT");

        Dependency dep2 = new Dependency();
        dep2.setArtifactId(item.getArtifactId());
        dep2.setClassifier("classifier");
        dep2.setGroupId(item.getGroupId());
        dep2.setType(item.getType());
        dep2.setVersion("2.1");

        List<Dependency> list = new ArrayList<>(2);
        list.add(dep2);
        list.add(dep);

        return list;
    }

    public void testMissingVersionFromDependencies() throws Exception {
        ArtifactItem item = new ArtifactItem();

        item.setArtifactId("artifactId");
        item.setClassifier("");
        item.setGroupId("groupId");
        item.setType("jar");

        List<ArtifactItem> list = new ArrayList<>();
        list.add(item);
        mojo.setArtifactItems(list);

        MavenProject project = mojo.getProject();
        project.setDependencies(createArtifacts(getDependencyList(item)));

        mojo.execute();
        assertMarkerFile(true, item);
        assertEquals("2.0-SNAPSHOT", item.getVersion());
    }

    public void testMissingVersionFromDependenciesWithClassifier() throws Exception {
        ArtifactItem item = new ArtifactItem();

        item.setArtifactId("artifactId");
        item.setClassifier("classifier");
        item.setGroupId("groupId");
        item.setType("war");

        List<ArtifactItem> list = new ArrayList<>();
        list.add(item);
        mojo.setArtifactItems(list);

        MavenProject project = mojo.getProject();
        project.setDependencies(createArtifacts(getDependencyList(item)));

        mojo.execute();
        assertMarkerFile(true, item);
        assertEquals("2.1", item.getVersion());
    }

    public List<Dependency> getDependencyMgtList(ArtifactItem item) {
        Dependency dep = new Dependency();
        dep.setArtifactId(item.getArtifactId());
        dep.setClassifier(item.getClassifier());
        dep.setGroupId(item.getGroupId());
        dep.setType(item.getType());
        dep.setVersion("3.0-SNAPSHOT");

        Dependency dep2 = new Dependency();
        dep2.setArtifactId(item.getArtifactId());
        dep2.setClassifier("classifier");
        dep2.setGroupId(item.getGroupId());
        dep2.setType(item.getType());
        dep2.setVersion("3.1");

        List<Dependency> list = new ArrayList<>(2);
        list.add(dep2);
        list.add(dep);

        return list;
    }

    public void testMissingVersionFromDependencyMgt() throws Exception {
        ArtifactItem item = new ArtifactItem();

        item.setArtifactId("artifactId");
        item.setClassifier("");
        item.setGroupId("groupId");
        item.setType("jar");

        MavenProject project = mojo.getProject();
        project.setDependencies(createArtifacts(getDependencyList(item)));

        item = new ArtifactItem();

        item.setArtifactId("artifactId-2");
        item.setClassifier("");
        item.setGroupId("groupId");
        item.setType("jar");

        List<ArtifactItem> list = new ArrayList<>();
        list.add(item);

        mojo.setArtifactItems(list);

        project.getDependencyManagement().setDependencies(createArtifacts(getDependencyMgtList(item)));

        mojo.execute();
        assertMarkerFile(true, item);
        assertEquals("3.0-SNAPSHOT", item.getVersion());
    }

    public void testMissingVersionFromDependencyMgtWithClassifier() throws Exception {
        ArtifactItem item = new ArtifactItem();

        item.setArtifactId("artifactId");
        item.setClassifier("classifier");
        item.setGroupId("groupId");
        item.setType("jar");

        MavenProject project = mojo.getProject();
        project.setDependencies(createArtifacts(getDependencyList(item)));

        item = new ArtifactItem();

        item.setArtifactId("artifactId-2");
        item.setClassifier("classifier");
        item.setGroupId("groupId");
        item.setType("jar");

        stubFactory.createArtifact(
                "groupId",
                "artifactId-2",
                VersionRange.createFromVersion("3.0-SNAPSHOT"),
                null,
                "jar",
                "classifier",
                false);
        stubFactory.createArtifact(
                "groupId", "artifactId-2", VersionRange.createFromVersion("3.1"), null, "jar", "classifier", false);

        List<ArtifactItem> list = new ArrayList<>();
        list.add(item);

        mojo.setArtifactItems(list);

        project.getDependencyManagement().setDependencies(createArtifacts(getDependencyMgtList(item)));

        mojo.execute();

        assertMarkerFile(true, item);
        assertEquals("3.1", item.getVersion());
    }

    public void testArtifactNotFound() throws Exception {
        dotestArtifactExceptions(false, true);
    }

    public void testArtifactResolutionException() throws Exception {
        dotestArtifactExceptions(true, false);
    }

    public void dotestArtifactExceptions(boolean are, boolean anfe) throws Exception {
        ArtifactItem item = new ArtifactItem();

        item.setArtifactId("artifactId");
        item.setClassifier("");
        item.setGroupId("groupId");
        item.setType("type");
        item.setVersion("1.0");

        List<ArtifactItem> list = new ArrayList<>();
        list.add(item);
        mojo.setArtifactItems(list);

        try {
            mojo.execute();
            fail("ExpectedException");
        } catch (MojoExecutionException e) {
            assertEquals("Unable to find/resolve artifact.", e.getMessage());
        }
    }

    public void testNoArtifactItems() {
        try {
            mojo.getProcessedArtifactItems(false);
            fail("Expected Exception");
        } catch (MojoExecutionException e) {
            assertEquals("There are no artifactItems configured.", e.getMessage());
        }
    }

    public void testUnpackDontOverWriteReleases() throws Exception {
        stubFactory.setCreateFiles(true);
        Artifact release = stubFactory.getReleaseArtifact();
        assertTrue(release.getFile().setLastModified(System.currentTimeMillis() - 2000));

        ArtifactItem item = new ArtifactItem(createArtifact(release));

        List<ArtifactItem> list = new ArrayList<>(1);
        list.add(item);
        mojo.setArtifactItems(list);

        mojo.setOverWriteIfNewer(false);

        mojo.execute();

        assertUnpacked(item, false);
    }

    public void testUnpackDontOverWriteSnapshots() throws Exception {
        stubFactory.setCreateFiles(true);
        Artifact artifact = stubFactory.getSnapshotArtifact();
        assertTrue(artifact.getFile().setLastModified(System.currentTimeMillis() - 2000));

        ArtifactItem item = new ArtifactItem(createArtifact(artifact));

        List<ArtifactItem> list = new ArrayList<>(1);
        list.add(item);
        mojo.setArtifactItems(list);

        mojo.setOverWriteIfNewer(false);

        mojo.execute();

        assertUnpacked(item, false);
    }

    public void testUnpackOverWriteReleases() throws Exception {
        stubFactory.setCreateFiles(true);
        Artifact release = stubFactory.getReleaseArtifact();
        assertTrue(release.getFile().setLastModified(System.currentTimeMillis() - 2000));

        ArtifactItem item = new ArtifactItem(createArtifact(release));

        List<ArtifactItem> list = new ArrayList<>(1);
        list.add(item);
        mojo.setArtifactItems(list);

        mojo.setOverWriteIfNewer(false);
        mojo.setOverWriteReleases(true);
        mojo.execute();

        assertUnpacked(item, true);
    }

    public void testUnpackOverWriteSnapshot() throws Exception {
        stubFactory.setCreateFiles(true);
        Artifact artifact = stubFactory.getSnapshotArtifact();
        assertTrue(artifact.getFile().setLastModified(System.currentTimeMillis() - 2000));

        ArtifactItem item = new ArtifactItem(createArtifact(artifact));

        List<ArtifactItem> list = new ArrayList<>(1);
        list.add(item);
        mojo.setArtifactItems(list);

        mojo.setOverWriteIfNewer(false);
        mojo.setOverWriteReleases(false);
        mojo.setOverWriteSnapshots(true);
        mojo.execute();

        assertUnpacked(item, true);
    }

    /**
     * The following code has been modified to prevent the
     * JDK bug which is described in detail
     * https://bugs.openjdk.java.net/browse/JDK-8177809
     *
     */
    public void testUnpackOverWriteIfNewer() throws Exception {
        final long now = System.currentTimeMillis();

        mojo.setSilent(false);
        stubFactory.setCreateFiles(true);
        Artifact artifact = stubFactory.getSnapshotArtifact();
        assertTrue(artifact.getFile().setLastModified(now - 20000));

        ArtifactItem item = new ArtifactItem(createArtifact(artifact));

        List<ArtifactItem> list = Collections.singletonList(item);
        mojo.setArtifactItems(list);
        mojo.setOverWriteIfNewer(true);
        mojo.execute();
        File unpackedFile = getUnpackedFile(item);

        // round down to the last second
        long time = now;
        time = time - (time % 1000);
        // go back 10 more seconds for linux
        time -= 10000;
        // set to known value
        assertTrue(unpackedFile.setLastModified(time));
        // set source to be newer about some seconds,
        // especially on macOS it shouldn't be smaller than 8s in order to mitigate flapping test
        assertTrue(artifact.getFile().setLastModified(time + 8000));

        // manually set markerfile (must match getMarkerFile in DefaultMarkerFileHandler)
        File marker = new File(mojo.getMarkersDirectory(), artifact.getId().replace(':', '-') + ".marker");
        assertTrue(marker.setLastModified(time));

        mojo.execute();

        long markerLastModifiedMillis =
                Files.getLastModifiedTime(marker.toPath()).toMillis();
        long unpackedFileLastModifiedMillis =
                Files.getLastModifiedTime(unpackedFile.toPath()).toMillis();

        assertTrue(
                "unpackedFile '" + unpackedFile + "' lastModified() == " + markerLastModifiedMillis
                        + ": should be different",
                markerLastModifiedMillis != unpackedFileLastModifiedMillis);
    }

    public void assertUnpacked(ArtifactItem item, boolean overWrite) throws Exception {

        File unpackedFile = getUnpackedFile(item);

        Thread.sleep(100);
        // round down to the last second
        long time = System.currentTimeMillis();
        time = time - (time % 1000);
        assertTrue(unpackedFile.setLastModified(time));

        assertEquals(time, unpackedFile.lastModified());
        mojo.execute();

        if (overWrite) {
            assertTrue(time != unpackedFile.lastModified());
        } else {
            assertEquals(time, unpackedFile.lastModified());
        }
    }

    public File getUnpackedFile(ArtifactItem item) {
        File unpackedFile = new File(
                item.getOutputDirectory(), DependencyArtifactStubFactory.getUnpackableFileName(item.getArtifact()));

        assertTrue(unpackedFile.exists());
        return unpackedFile;
    }

    // respects the createUnpackableFile flag of the ArtifactStubFactory
    private List<Dependency> createArtifacts(List<Dependency> items) throws IOException {
        for (Dependency item : items) {
            String classifier = "".equals(item.getClassifier()) ? null : item.getClassifier();
            stubFactory.createArtifact(
                    item.getGroupId(),
                    item.getArtifactId(),
                    VersionRange.createFromVersion(item.getVersion()),
                    null,
                    item.getType(),
                    classifier,
                    item.isOptional());
        }
        return items;
    }

    private Artifact createArtifact(Artifact art) throws IOException {
        String classifier = "".equals(art.getClassifier()) ? null : art.getClassifier();
        stubFactory.createArtifact(
                art.getGroupId(),
                art.getArtifactId(),
                VersionRange.createFromVersion(art.getVersion()),
                null,
                art.getType(),
                classifier,
                art.isOptional());
        return art;
    }

    private ArtifactItem createArtifact(ArtifactItem item) throws IOException {
        String classifier = "".equals(item.getClassifier()) ? null : item.getClassifier();
        stubFactory.createArtifact(
                item.getGroupId(), item.getArtifactId(), item.getVersion(), null, item.getType(), classifier);
        return item;
    }
}
