/*
 * 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.it;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Deque;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;

import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * This is a test set for <a href="https://issues.apache.org/jira/browse/MNG-3415">MNG-3415</a>.
 */
public class MavenITmng3415JunkRepositoryMetadataTest extends AbstractMavenIntegrationTestCase {
    private static final String RESOURCE_BASE = "/mng-3415";

    public MavenITmng3415JunkRepositoryMetadataTest() {
        // we're going to control the test execution according to the maven version present within each test method.
        // all methods should execute as long as we're using maven 2.0.9+, but the specific tests may vary a little
        // depending on which version we're using above 2.0.8.
        super("(2.0.8,)"); // only test in 2.0.9+
    }

    /**
     * This test simply verifies that when a metadata transfer fails (network error, etc.)
     * no metadata file is written to the local repository.
     * <p>
     * Steps executed to verify this test:
     * </p>
     * 0. Find the local repository directory:
     * a. build the maven-find-local-repo-plugin, then run it, to spit out the path of the
     * local repository in use by default. Read the output file to get this path.
     * (Yes, it's heavy, but it's reliable.)
     * 1. Setup the test:
     * a. Make sure the metadata for the test-repo is NOT in the local repository.
     * b. Make sure the dependency POM IS in the local repository, so we're not
     * distracted by failed builds that are unrelated.
     * c. Create the settings file for use in this test, which contains the invalid
     * remote repository entry.
     * 2. Build the test project the first time
     * a. Verify that a TransferFailedException is in the build output for the test-repo
     * b. Verify that the metadata for the dependency POM is NOT in the local
     * repository afterwards.
     * 3. Build the test project the second time
     * a. See (2.a) and (2.b) above; the same criteria applies here.
     *
     * @throws Exception in case of failure
     */
    @Test
    public void testitTransferFailed() throws Exception {
        String methodName = getMethodName();

        File testDir = extractResources(RESOURCE_BASE);

        Verifier verifier;

        verifier = newVerifier(testDir.getAbsolutePath());
        verifier.setAutoclean(false);
        verifier.deleteArtifacts("org.apache.maven.its.mng3415");

        setupDummyDependency(verifier, testDir, true);

        Map<String, String> filterProps = verifier.newDefaultFilterMap();
        filterProps.put("@protocol@", "invalid");
        filterProps.put("@port@", "0");
        File settings = verifier.filterFile("settings-template.xml", "settings-a.xml", filterProps);

        verifier.addCliArgument("-X");
        verifier.addCliArgument("-s");
        verifier.addCliArgument(settings.getName());

        verifier.setLogFileName("log-" + methodName + "-firstBuild.txt");
        verifier.addCliArgument("validate");
        verifier.execute();

        verifier.verifyErrorFreeLog();

        assertMetadataMissing(verifier);

        setupDummyDependency(verifier, testDir, true);

        verifier.setLogFileName("log-" + methodName + "-secondBuild.txt");
        verifier.addCliArgument("validate");
        verifier.execute();

        verifier.verifyErrorFreeLog();

        assertMetadataMissing(verifier);
    }

    private String getMethodName() {
        return new Throwable().getStackTrace()[1].getMethodName();
    }

    /**
     * This test simply verifies that when metadata doesn't exist on the remote
     * repository, a basic metadata file is written to the local repository.
     * <p>
     * Steps executed to verify this test:
     * </p>
     * 0. Find the local repository directory:
     * a. build the maven-find-local-repo-plugin, then run it, to spit out the path of the
     * local repository in use by default. Read the output file to get this path.
     * (Yes, it's heavy, but it's reliable.)
     * 1. Setup the test:
     * a. Make sure the metadata for the test-repo is NOT in the local repository.
     * b. Make sure the dependency POM IS in the local repository, so we're not
     * distracted by failed builds that are unrelated.
     * c. Create the settings file for use in this test, which contains the VALID
     * remote repository entry.
     * 2. Build the test project the first time
     * a. Verify that the remote repository is checked for the metadata file
     * 3. Build the test project the second time
     * a. Verify that the remote repository is NOT checked for the metadata file again
     * b. Verify that the file used for updateInterval calculations was NOT changed from
     * the first build.
     *
     * @throws Exception in case of failure
     */
    @Test
    public void testShouldNotRepeatedlyUpdateOnResourceNotFoundException() throws Exception {
        String methodName = getMethodName();

        File testDir = extractResources(RESOURCE_BASE);

        Verifier verifier;

        verifier = newVerifier(testDir.getAbsolutePath());
        verifier.setAutoclean(false);
        verifier.deleteArtifacts("org.apache.maven.its.mng3415");

        final Deque<String> requestUris = new ConcurrentLinkedDeque<>();

        Handler repoHandler = new AbstractHandler() {
            @Override
            public void handle(
                    String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) {
                System.out.println("Handling " + request.getMethod() + " " + request.getRequestURL());

                requestUris.add(request.getRequestURI());

                response.setStatus(HttpServletResponse.SC_NOT_FOUND);

                ((Request) request).setHandled(true);
            }
        };

        Server server = new Server(0);
        server.setHandler(repoHandler);

        try {
            server.start();
            if (server.isFailed()) {
                fail("Couldn't bind the server socket to a free port!");
            }

            int port = ((NetworkConnector) server.getConnectors()[0]).getLocalPort();
            System.out.println("Bound server socket to the port " + port);

            Map<String, String> filterProps = verifier.newDefaultFilterMap();
            filterProps.put("@protocol@", "http");
            filterProps.put("@port@", Integer.toString(port));
            File settings = verifier.filterFile("settings-template.xml", "settings-b.xml", filterProps);

            verifier.addCliArgument("-X");
            verifier.addCliArgument("-s");
            verifier.addCliArgument(settings.getName());

            setupDummyDependency(verifier, testDir, true);

            verifier.setLogFileName("log-" + methodName + "-firstBuild.txt");
            verifier.addCliArgument("validate");
            verifier.execute();

            verifier.verifyErrorFreeLog();

            assertTrue(
                    requestUris.contains("/org/apache/maven/its/mng3415/missing/1.0-SNAPSHOT/maven-metadata.xml"),
                    requestUris.toString());

            requestUris.clear();

            File updateCheckFile = getUpdateCheckFile(verifier);
            long firstLastMod = updateCheckFile.lastModified();

            setupDummyDependency(verifier, testDir, false);

            verifier.setLogFileName("log-" + methodName + "-secondBuild.txt");
            verifier.addCliArgument("validate");
            verifier.execute();

            verifier.verifyErrorFreeLog();

            assertFalse(
                    requestUris.contains("/org/apache/maven/its/mng3415/missing/1.0-SNAPSHOT/maven-metadata.xml"),
                    requestUris.toString());

            assertEquals(
                    firstLastMod,
                    updateCheckFile.lastModified(),
                    "Last-modified time should be unchanged from first build through second build for the file we use for"
                            + " updateInterval checks.");
        } finally {
            server.stop();
            server.join();
        }
    }

    private void assertMetadataMissing(Verifier verifier) {
        File metadata = getMetadataFile(verifier);

        assertFalse(
                metadata.exists(),
                "Metadata file should NOT be present in local repository: " + metadata.getAbsolutePath());
    }

    private void setupDummyDependency(Verifier verifier, File testDir, boolean resetUpdateInterval) throws IOException {
        String gid = "org.apache.maven.its.mng3415";
        String aid = "missing";
        String version = "1.0-SNAPSHOT";

        if (resetUpdateInterval) {
            verifier.deleteArtifacts(gid);
        }

        File pom = new File(verifier.getArtifactPath(gid, aid, version, "pom"));

        File pomSrc = new File(testDir, "dependency-pom.xml");

        System.out.println("Copying dependency POM\nfrom: " + pomSrc + "\nto: " + pom);
        Files.createDirectories(pom.toPath().getParent());
        Files.copy(pomSrc.toPath(), pom.toPath(), StandardCopyOption.REPLACE_EXISTING);
    }

    private File getMetadataFile(Verifier verifier) {
        String gid = "org.apache.maven.its.mng3415";
        String aid = "missing";
        String version = "1.0-SNAPSHOT";
        String name = "maven-metadata-testing-repo.xml";

        return new File(verifier.getArtifactMetadataPath(gid, aid, version, name));
    }

    /**
     * If the current maven version is < 3.0, we'll use the metadata file itself (old maven-artifact code)...
     * otherwise, use the new resolver-status.properties file (new artifact code).
     */
    private File getUpdateCheckFile(Verifier verifier) {
        String gid = "org.apache.maven.its.mng3415";
        String aid = "missing";
        String version = "1.0-SNAPSHOT";
        String name;

        // < 3.0 (including snapshots)
        if (matchesVersionRange("(2.0.8,3.0-alpha-1)")) {
            name = "maven-metadata-testing-repo.xml";
        } else {
            name = "resolver-status.properties";
        }

        return new File(verifier.getArtifactMetadataPath(gid, aid, version, name));
    }
}
