package e2e;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static scaffolding.ExactCountMatcher.oneOf;
import static scaffolding.ExactCountMatcher.twoOf;
import static scaffolding.GitMatchers.hasCleanWorkingDirectory;
import static scaffolding.GitMatchers.hasTag;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.apache.maven.shared.invoker.MavenInvocationException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import scaffolding.MavenExecutionException;
import scaffolding.MvnRunner;
import scaffolding.TestProject;

public class ValidationTest {

    @BeforeClass
    public static void installPluginToLocalRepo() throws MavenInvocationException {
        MvnRunner.installReleasePluginToLocalRepo();
    }

    @Test
    public void ifTheSameVersionIsReleasedTwiceItErrorsLoudly() throws Exception {
        TestProject testProject = TestProject.singleModuleProject();
        testProject.mvnRelease("1");
        testProject.commitRandomFile(".").pushIt();
        try {
            testProject.mvnRelease("1");
            Assert.fail("Should not have completed running");
        } catch (MavenExecutionException mee) {
            assertThat(mee.output,
                twoOf(containsString("There is already a tag named single-module-1.0.1 in this repository.")));
            assertThat(mee.output,
                oneOf(containsString("It is likely that this version has been released before.")));
            assertThat(mee.output,
                oneOf(containsString("Please try incrementing the build number and trying again.")));
        }
    }

    @Test
    public void ifAReleaseTagAlreadyExistsInTheRemoteRepoThenItErrorsEarly() throws Exception {
        TestProject testProject = TestProject.singleModuleProject();
        testProject.origin.tag()
            .setAnnotated(true).setName("single-module-1.0.1").setMessage("Simulating remote tag").call();
        try {
            testProject.mvnRelease("1");
            Assert.fail("Should not have completed running");
        } catch (MavenExecutionException mee) {
            assertThat(mee.output, twoOf(containsString("Cannot release because there is already a tag with the same build number on the remote Git repo.")));
            assertThat(mee.output, oneOf(containsString("* There is already a tag named single-module-1.0.1 in the remote repo.")));
            assertThat(mee.output, oneOf(containsString("Please try releasing again with a new build number.")));
        }
    }

    @Test
    public void failsIfThereAreUntrackedFiles() throws IOException, InterruptedException {
        TestProject testProject = TestProject.singleModuleProject();
        new File(testProject.localDir, "untracked.txt").createNewFile();
        new File(testProject.localDir, "someFolder").mkdir();
        new File(testProject.localDir, "someFolder/anotherUntracked.txt").createNewFile();
        try {
            testProject.mvnRelease("1");
            Assert.fail("Should not have worked the second time");
        } catch (MavenExecutionException mee) {
            assertThat(mee.output, twoOf(containsString("Cannot release with uncommitted changes")));
            assertThat(mee.output, oneOf(containsString(" * untracked.txt")));
            assertThat(mee.output, oneOf(containsString(" * someFolder/anotherUntracked.txt")));
        }
    }

    @Test
    public void failsIfThereAreUncommittedFiles() throws IOException, InterruptedException, GitAPIException {
        TestProject testProject = TestProject.singleModuleProject();
        new File(testProject.localDir, "uncommitted.txt").createNewFile();
        testProject.local.add().addFilepattern("uncommitted.txt").call();
        try {
            testProject.mvnRelease("1");
            Assert.fail("Should not have worked as there are uncommitted files");
        } catch (MavenExecutionException mee) {
            assertThat(mee.output, twoOf(containsString("Cannot release with uncommitted changes")));
            assertThat(mee.output, oneOf(containsString(" * uncommitted.txt")));
        }
    }

    @Test
    public void ifIOErrorOccursWhileUpdatingPomsThenThisIsReported() throws IOException, InterruptedException {
        TestProject testProject = TestProject.independentVersionsProject();
        File pom = new File(testProject.localDir, "console-app/pom.xml");
        pom.setWritable(false); // this should cause an IO exception when writing the pom
        try {
            testProject.mvnRelease("1");
            Assert.fail("It was expected that this would fail due to a pom being readonly.");
        } catch (MavenExecutionException e) {
            assertThat(e.output, twoOf(containsString("Unexpected exception while setting the release versions in the pom")));
            assertThat(e.output, oneOf(containsString("Going to revert changes because there was an error")));
        }
        assertThat(testProject.local, hasCleanWorkingDirectory());
    }

    @Test
    public void failsIfThereAreDependenciesOnSnapshotVersionsThatAreNotPartOfTheReactor() throws Exception {
        // Install the snapshot dependency so that it can be built
        TestProject dependency = TestProject.independentVersionsProject();
        dependency.mvn("install");

        TestProject badOne = TestProject.moduleWithSnapshotDependencies();

        badOne.mvn("install"); // this should work as the snapshot dependency is in the local repo

        try {
            badOne.mvnRelease("1");
            Assert.fail("Should not have worked as there are snapshot dependencies");
        } catch (MavenExecutionException mee) {
            assertThat(mee.output, twoOf(containsString("Cannot release with references to snapshot dependencies")));
            assertThat(mee.output, oneOf(containsString("The following dependency errors were found:")));
            assertThat(mee.output, oneOf(containsString(" * The parent of snapshot-dependencies is independent-versions 1.0-SNAPSHOT")));
            assertThat(mee.output, oneOf(containsString(" * snapshot-dependencies references dependency core-utils 2.0-SNAPSHOT")));

            // commented out because this plugin is allowed to be a snapshot for testing purposes only
//            assertThat(mee.output, oneOf(containsString(" * snapshot-dependencies references plugin multi-module-maven-release-plugin 0.2-SNAPSHOT")));
        }

        assertThat(badOne.local, hasCleanWorkingDirectory());
    }

    @Test
    public void failsIfThereAreDependenciesOnSnapshotVersionsWithVersionPropertiesThatAreNotPartOfTheReactor() throws Exception {
        // Install the snapshot dependency so that it can be built
        TestProject dependency = TestProject.independentVersionsProject();
        dependency.mvn("install");
    	
        TestProject badOne = TestProject.moduleWithSnapshotDependenciesWithVersionProperties();

        badOne.mvn("install"); // this should work as the snapshot dependency is in the local repo

        try {
            badOne.mvnRelease("1");
            Assert.fail("Should not have worked as there are snapshot dependencies");
        } catch (MavenExecutionException mee) {
            assertThat(mee.output, twoOf(containsString("Cannot release with references to snapshot dependencies")));
            assertThat(mee.output, oneOf(containsString("The following dependency errors were found:")));
            assertThat(mee.output, oneOf(containsString(" * snapshot-dependencies-with-version-properties references dependency core-utils ${core-utils.version}")));
        }

        assertThat(badOne.local, hasCleanWorkingDirectory());
    }

    @Test
    public void passIfThereAreUntrackedFilesWhichAreAddedToUntrackedConfiguration() throws Exception {
        TestProject testProject = TestProject.singleModuleProject();
        new File(testProject.localDir, "untracked1.txt").createNewFile();
        new File(testProject.localDir, "someFolder").mkdir();
        new File(testProject.localDir, "someFolder/untracked2.txt").createNewFile();

        String buildNumber = "1";
        String expected = "1.0." + buildNumber;
        List<String> outputLines = testProject.mvnRelease(buildNumber, "-DignoredUntrackedPaths=\"someFolder/untracked2.txt,untracked1.txt\"");
        assertThat(outputLines, oneOf(containsString("Going to release single-module " + expected)));
        assertThat(testProject.local, hasTag("single-module-" + expected));
    }
}
