package org.jenkinsci.plugins.sma;

import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.CreateBranchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class SMAGitTest
{

    private Repository repository;
    private SMAGit git;
    private File addition, addMeta;
    private File modification, modifyMeta;
    private File deletion, deleteMeta;
    private File localPath;
    private String oldSha, newSha, gitDir;
    private final String contents = "\n";

    /**
     * Before to setup the test.
     *
     * @throws Exception
     */
    @Before
    public void setUp() throws Exception
    {
        //Setup the fake repository
        localPath = File.createTempFile("TestGitRepository", "");
        localPath.delete();
        repository = FileRepositoryBuilder.create(new File(localPath, ".git"));
        repository.create();

        File classesPath = new File(repository.getDirectory().getParent() + "/src/classes");
        classesPath.mkdirs();
        File pagesPath = new File(repository.getDirectory().getParent() + "/src/pages");
        pagesPath.mkdirs();
        File triggersPath = new File(repository.getDirectory().getParent() + "/src/triggers");
        triggersPath.mkdirs();


        //Add the first collection of files
        deletion = createFile("deleteThis.cls", classesPath);
        deleteMeta = createFile("deleteThis.cls-meta.xml", classesPath);
        modification = createFile("modifyThis.page", pagesPath);
        modifyMeta = createFile("modifyThis.page-meta.xml", pagesPath);
        new Git(repository).add().addFilepattern("src/classes/deleteThis.cls").call();
        new Git(repository).add().addFilepattern("src/classes/deleteThis.cls-meta.xml").call();
        new Git(repository).add().addFilepattern("src/pages/modifyThis.page").call();
        new Git(repository).add().addFilepattern("src/pages/modifyThis.page-meta.xml").call();

        //Create the first commit
        RevCommit firstCommit = new Git(repository).commit().setMessage("Add deleteThis and modifyThis").call();
        oldSha = firstCommit.getName();


        //Delete the deletion file, modify the modification file, and add the addition file
        new Git(repository).rm().addFilepattern("src/classes/deleteThis.cls").call();
        new Git(repository).rm().addFilepattern("src/classes/deleteThis.cls-meta.xml").call();
        modification.setExecutable(true);
        addition = createFile("addThis.trigger", triggersPath);
        addMeta = createFile("addThis.trigger-meta.xml", triggersPath);
        new Git(repository).add().addFilepattern("src/pages/modifyThis.page").call();
        new Git(repository).add().addFilepattern("src/pages/modifyThis.page-meta.xml").call();
        new Git(repository).add().addFilepattern("src/triggers/addThis.trigger").call();
        new Git(repository).add().addFilepattern("src/triggers/addThis.trigger-meta.xml").call();
        new Git(repository).add().addFilepattern("src/classes/deleteThis.cls").call();
        new Git(repository).add().addFilepattern("src/classes/deleteThis.cls-meta.xml").call();

        //Create the second commit
        RevCommit secondCommit = new Git(repository).commit().setMessage("Remove deleteThis. Modify " +
                "modifyThis. Add addThis.").call();
        newSha = secondCommit.getName();

        gitDir = localPath.getPath();
    }

    /**
     * After to tear down the test.
     *
     * @throws Exception
     */
    @After
    public void tearDown() throws Exception
    {
        repository.close();
        FileUtils.deleteDirectory(localPath);
    }

    /**
     * Test the diff capability of the wrapper.
     *
     * @throws Exception
     */
    @Test
    public void testDiff() throws Exception
    {
        Map<String, byte[]> expectedDelete = new HashMap<String, byte[]>();
        expectedDelete.put("src/classes/deleteThis.cls", contents.getBytes());

        Map<String, byte[]> expectedMods = new HashMap<String, byte[]>();
        expectedMods.put("src/pages/modifyThis.page", contents.getBytes());

        Map<String, byte[]> expectedAdds = new HashMap<String, byte[]>();
        expectedAdds.put("src/triggers/addThis.trigger", contents.getBytes());

        git = new SMAGit(gitDir, newSha, oldSha, SMAGit.Mode.STD);

        Map<String, byte[]> deletedContents = git.getDeletedMetadata();
        Map<String, byte[]> modifiedContents = git.getUpdatedMetadata();
        Map<String, byte[]> addedContents = git.getNewMetadata();

        assertEquals(expectedAdds.size(), addedContents.size());
        assertEquals(expectedMods.size(), modifiedContents.size());
        assertEquals(expectedDelete.size(), deletedContents.size());
    }

    /**
     * Test the overloaded constructors.
     *
     * @throws Exception
     */
    @Test
    public void testInitialCommit() throws Exception
    {
        Map<String, byte[]> expectedContents = new HashMap<String, byte[]>();
        expectedContents.put("src/pages/modifyThis.page", contents.getBytes());
        expectedContents.put("src/pages/modifyThis.page-meta.xml", contents.getBytes());
        expectedContents.put("src/triggers/addThis.trigger", contents.getBytes());
        expectedContents.put("src/triggers/addThis.trigger-meta.xml", contents.getBytes());

        git = new SMAGit(gitDir, newSha, null, SMAGit.Mode.INI);

        Map<String, byte[]> allMetadata = git.getAllMetadata();

        assertEquals(expectedContents.size(), allMetadata.size());
    }

    /**
     * Test the ghprb constructor.
     *
     * @throws Exception
     */
    @Test
    public void testPullRequest() throws Exception
    {
        Map<String, byte[]> expectedContents = new HashMap<String, byte[]>();
        expectedContents.put("src/pages/modifyThis.page", contents.getBytes());
        expectedContents.put("src/pages/modifyThis.page-meta.xml", contents.getBytes());
        expectedContents.put("src/triggers/addThis.trigger", contents.getBytes());
        expectedContents.put("src/triggers/addThis.trigger-meta.xml", contents.getBytes());

        String oldBranch = "refs/remotes/origin/oldBranch";
        CreateBranchCommand cbc = new Git(repository).branchCreate();
        cbc.setName(oldBranch);
        cbc.setStartPoint(oldSha);
        cbc.call();

        git = new SMAGit(gitDir, newSha, "oldBranch", SMAGit.Mode.PRB);

        Map<String, byte[]> allMetadata = git.getAllMetadata();

        assertEquals(expectedContents.size(), allMetadata.size());
    }

    /**
     * Test the ability to update the package manifest.
     *
     * @throws Exception
     */
    @Test
    public void testCommitPackageXML() throws Exception
    {
        Map<String, byte[]> metadataContents = new HashMap<String, byte[]>();
        List<SMAMetadata> metadata = new ArrayList<SMAMetadata>();

        git = new SMAGit(gitDir, newSha, oldSha, SMAGit.Mode.STD);
        metadataContents = git.getUpdatedMetadata();
        metadataContents.putAll(git.getNewMetadata());

        for (String s : metadataContents.keySet())
        {
            metadata.add(SMAMetadataTypes.createMetadataObject(s, metadataContents.get(s)));
        }

        SMAPackage manifest = new SMAPackage(metadata, false);

        Boolean createdManifest = git.updatePackageXML(
                localPath.getPath(),
                "Test Guy",
                "[email protected]",
                manifest
        );

        assertTrue(createdManifest);
    }

    /**
     * Test the ability to update the package manifest.
     *
     * @throws Exception
     */
    @Test
    public void testCommitExistingPackage() throws Exception
    {
        File sourceDir = new File(localPath.getPath() + "/src");
        File existingPackage = createFile("package.xml", sourceDir);

        new Git(repository).add().addFilepattern("src/package.xml").call();
        new Git(repository).commit().setMessage("Add package.xml").call();

        Map<String, byte[]> metadataContents = new HashMap<String, byte[]>();
        List<SMAMetadata> metadata = new ArrayList<SMAMetadata>();

        git = new SMAGit(gitDir, newSha, oldSha, SMAGit.Mode.STD);
        metadataContents = git.getUpdatedMetadata();
        metadataContents.putAll(git.getNewMetadata());

        for (String s : metadataContents.keySet())
        {
            metadata.add(SMAMetadataTypes.createMetadataObject(s, metadataContents.get(s)));
        }

        SMAPackage manifest = new SMAPackage(metadata, false);

        Boolean createdManifest = git.updatePackageXML(
                localPath.getPath(),
                "Test Guy",
                "[email protected]",
                manifest
        );

        assertTrue(createdManifest);

        // Also check to make sure we didn't create the default package
        File unexpectedPackage = new File(localPath.getPath() + "/unpackaged/package.xml");
        assertTrue(!unexpectedPackage.exists());
    }

    private File createFile(String name, File path) throws Exception
    {
        File thisFile;

        thisFile = new File(path, name);
        thisFile.createNewFile();

        PrintWriter print = new PrintWriter(thisFile);
        print.println(contents);
        print.close();

        return thisFile;
    }
}