/*
 * Licensed to DuraSpace under one or more contributor license agreements.
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.
 *
 * DuraSpace 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.fcrepo.importexport.integration;

import static org.apache.http.HttpStatus.SC_CREATED;
import static org.apache.http.HttpStatus.SC_NO_CONTENT;
import static org.apache.jena.graph.NodeFactory.createLiteral;
import static org.apache.jena.graph.NodeFactory.createURI;
import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel;
import static org.apache.jena.rdf.model.ResourceFactory.createResource;
import static org.fcrepo.importexport.common.Config.DEFAULT_RDF_LANG;
import static org.fcrepo.importexport.common.FcrepoConstants.CONTAINS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.slf4j.LoggerFactory.getLogger;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.UUID;

import org.apache.jena.graph.Graph;
import org.fcrepo.client.FcrepoClient;
import org.fcrepo.client.FcrepoOperationFailedException;
import org.fcrepo.client.FcrepoResponse;
import org.fcrepo.importexport.common.Config;
import org.fcrepo.importexport.exporter.Exporter;
import org.fcrepo.importexport.importer.Importer;

import org.apache.commons.io.IOUtils;
import org.apache.jena.rdf.model.Model;
import org.junit.Test;
import org.slf4j.Logger;

/**
 * @author awoods
 * @since 2016-09-18
 */
public class ImporterIT extends AbstractResourceIT {

    private FcrepoClient client;
    private String[] predicates = new String[]{ CONTAINS.toString() };

    public ImporterIT() {
        super();
        client = clientBuilder.build();
    }

    @Test
    public void testImport() throws FcrepoOperationFailedException, IOException {
        final String exportPath = TARGET_DIR + "/" + UUID.randomUUID() + "/testPeartreeAmbiguity";
        final String parentTitle = "parent";
        final String childTitle = "child";
        final String binaryText = "binary";

        final URI parent = URI.create(serverAddress + UUID.randomUUID());
        final URI child = URI.create(parent.toString() + "/child");
        final URI binary = URI.create(child + "/binary");
        assertEquals(SC_CREATED, create(parent).getStatusCode());
        assertEquals(SC_CREATED, create(child).getStatusCode());
        assertEquals(SC_NO_CONTENT, client.patch(parent).body(insertTitle(parentTitle)).perform().getStatusCode());
        assertEquals(SC_NO_CONTENT, client.patch(child).body(insertTitle(childTitle)).perform().getStatusCode());
        assertEquals(SC_CREATED, client.put(binary).body(new ByteArrayInputStream(binaryText.getBytes("UTF-8")),
                "text/plain").perform().getStatusCode());

        // Run an export process
        final Config config = new Config();
        config.setMode("export");
        config.setBaseDirectory(exportPath);
        config.setIncludeBinaries(true);
        config.setPredicates(predicates);
        config.setRdfLanguage(DEFAULT_RDF_LANG);
        config.setResource(parent.toString());
        config.setUsername(USERNAME);
        config.setPassword(PASSWORD);

        final Exporter exporter = new Exporter(config, clientBuilder);
        exporter.run();

        // Verify
        resourceExists(parent);

        // Remove the resources
        removeAndReset(parent);

        // Run the import process
        config.setMode("import");

        final Importer importer = new Importer(config, clientBuilder);
        importer.run();

        // Verify
        assertHasTitle(parent, parentTitle);
        assertHasTitle(child, childTitle);
        assertEquals("Binary should have been imported!",
                binaryText, IOUtils.toString(client.get(binary).perform().getBody(), "UTF-8"));
    }

    @Test
    public void testCorruptedBinary() throws Exception {
        final URI sourceURI = URI.create(serverAddress);
        final URI binaryURI = URI.create(serverAddress + "/bin1");
        final String referencePath = TARGET_DIR + "/test-classes/sample/corrupted";
        System.out.println("Importing from " + referencePath);

        final Config config = new Config();
        config.setMode("import");
        config.setIncludeBinaries(true);
        config.setBaseDirectory(referencePath);
        config.setRdfLanguage(DEFAULT_RDF_LANG);
        config.setResource(serverAddress);
        config.setMap(new String[]{sourceURI.toString(), serverAddress});
        config.setUsername(USERNAME);
        config.setPassword(PASSWORD);

        // run import
        final Importer importer = new Importer(config, clientBuilder);
        importer.run();

        // verify that the corrupted binary failed to load
        assertFalse(resourceExists(binaryURI));
    }

    @Test
    public void testImportRelatedResources() throws Exception {
        final String sourceServerAddress = "http://localhost:8080/fcrepo/rest";
        final URI linkFrom = URI.create(serverAddress + "linkFrom");
        final URI linkTo = URI.create(serverAddress + "linkTo");
        final URI linkToFile1 = URI.create(serverAddress + "linkTo/file1");
        final String referencePath = TARGET_DIR + "/test-classes/sample/reference";
        logger().debug("Importing from {}", referencePath);

        final String[] providedPredicates = new String[] { "http://purl.org/dc/terms/relation" };
        final Config config = new Config();
        config.setMode("import");
        config.setIncludeBinaries(true);
        config.setBaseDirectory(referencePath);
        config.setPredicates(providedPredicates);
        config.setRdfLanguage(DEFAULT_RDF_LANG);
        config.setResource(linkFrom.toString());
        config.setMap(new String[]{sourceServerAddress, serverAddress});
        config.setUsername(USERNAME);
        config.setPassword(PASSWORD);

        // run import
        final Importer importer = new Importer(config, clientBuilder);
        importer.run();

        // verify the resources exist and link to each other
        assertTrue(resourceExists(linkFrom));
        assertTrue(resourceExists(linkTo));
        assertTrue(resourceExists(linkToFile1));
        assertTrue(resourceLinksTo(linkTo, linkFrom));
        assertTrue(resourceLinksTo(linkFrom, linkTo));

        // verify member resource content
        final FcrepoResponse response = clientBuilder.build().get(linkTo).accept("text/plain").perform();
        final String triples = IOUtils.toString(response.getBody());
        final String hasfileTriple = "<" + linkTo + "> <http://pcdm.org/models#hasFile> <" + linkToFile1 + "> .";
        assertEquals(1, count(triples, hasfileTriple));
    }

    @Test
    public void testReferences() throws Exception {
        final URI sourceURI = URI.create("http://localhost:8080/fcrepo/rest");
        final URI linkFrom = URI.create(serverAddress + "prod2/linkFrom");
        final URI linkTo = URI.create(serverAddress + "prod2/linkTo");
        final String referencePath = TARGET_DIR + "/test-classes/sample/reference";
        logger().debug("Importing from {}", referencePath);

        final Config config = new Config();
        config.setMode("import");
        config.setIncludeBinaries(true);
        config.setBaseDirectory(referencePath);
        config.setPredicates(predicates);
        config.setRdfLanguage(DEFAULT_RDF_LANG);
        config.setResource(serverAddress);
        config.setMap(new String[]{sourceURI.toString(), serverAddress + "prod2/"});
        config.setUsername(USERNAME);
        config.setPassword(PASSWORD);

        // run import
        final Importer importer = new Importer(config, clientBuilder);
        importer.run();

        // verify the resources exist and link to each other
        assertTrue(resourceExists(linkFrom));
        assertTrue(resourceExists(linkTo));
        assertTrue(resourceLinksTo(linkTo, linkFrom));
        assertTrue(resourceLinksTo(linkFrom, linkTo));
    }

    @Test
    public void testImportContainer() throws Exception {
        // import test-indirect

        final URI sourceURI = URI.create("http://localhost:8080/fcrepo/rest");
        final URI parentURI = URI.create(serverAddress + "indirect/1");
        final URI memberURI = URI.create(serverAddress + "indirect/2");
        final String indirectPath = TARGET_DIR + "/test-classes/sample/indirect";

        final Config config = new Config();
        config.setMode("import");
        config.setBaseDirectory(indirectPath);
        config.setPredicates(predicates);
        config.setRdfLanguage(DEFAULT_RDF_LANG);
        config.setResource(serverAddress);
        config.setMap(new String[]{sourceURI.toString(), serverAddress});
        config.setUsername(USERNAME);
        config.setPassword(PASSWORD);

        // run import
        final Importer importer = new Importer(config, clientBuilder);
        importer.run();

        // verify one title and one hasMember
        final FcrepoResponse response = clientBuilder.build().get(parentURI).accept("application/n-triples").perform();
        final Model model = createDefaultModel();
        model.read(response.getBody(), "", "N3");
        response.close();
        final Graph graph = model.getGraph();
        assertTrue("DC title should exist. \n" + graph.toString(), graph.contains(createURI(String.valueOf(parentURI)),
                createURI("http://purl.org/dc/terms/title"), createLiteral("foo")));
        assertTrue("Membership triple should exist.", graph.contains(createURI(String.valueOf(parentURI)),
                createURI("http://pcdm.org/models#hasMember"), createURI(String.valueOf(memberURI))));
    }

    @Test
    public void testImportMapped() throws Exception {
        final URI sourceURI = URI.create("http://localhost:8080/rest/dev/asdf");
        final URI resourceURI = URI.create(serverAddress + "prod/asdf");

        final Config config = new Config();
        config.setMode("import");
        config.setBaseDirectory(TARGET_DIR + "/test-classes/sample/mapped");
        config.setResource(resourceURI.toString());
        config.setMap(new String[]{sourceURI.toString(), serverAddress + "prod/asdf"});
        config.setUsername(USERNAME);
        config.setPassword(PASSWORD);

        // run import
        final Importer importer = new Importer(config, clientBuilder);
        importer.run();

        // verify one title and one hasMember
        assertTrue(resourceExists(resourceURI));
    }

    @Test
    public void testImportMappedTrailingSlash() throws Exception {
        final URI sourceURI = URI.create("http://localhost:8080/rest/dev/");
        final URI resourceURI = URI.create(serverAddress + "prod2/");
        final URI childURI = URI.create(serverAddress + "prod2/asdf/");

        final Config config = new Config();
        config.setMode("import");
        config.setBaseDirectory(TARGET_DIR + "/test-classes/sample/mapped");
        config.setResource(childURI.toString());
        config.setMap(new String[]{sourceURI.toString(), resourceURI.toString()});
        config.setUsername(USERNAME);
        config.setPassword(PASSWORD);

        // run import
        final Importer importer = new Importer(config, clientBuilder);
        importer.run();

        // verify one title and one hasMember
        assertTrue(resourceExists(childURI));
    }

    @Test
    public void testRepositoryRoot() throws Exception {
        final URI rest = URI.create(serverAddress);
        final URI restFoo = URI.create(serverAddress + "foo");
        final URI restFooBar = URI.create(serverAddress + "foo/bar");
        final URI root = URI.create(serverAddress.substring(0, serverAddress.length() - 1));

        final Config config = new Config();
        config.setMode("import");
        final Importer importer = new Importer(config, clientBuilder);

        assertEquals(root, importer.findRepositoryRoot(rest));
        assertEquals(root, importer.findRepositoryRoot(restFoo));
        assertEquals(root, importer.findRepositoryRoot(restFooBar));
    }

    private int count(final String triples, final String triple) {
        int count = 0;
        final String[] arr = triples.split("\\n");
        for (int i = 0; i < arr.length; i++) {
            if (triple.equals(arr[i])) {
                count++;
            }
        }
        return count;
    }

    private boolean resourceExists(final URI uri) throws FcrepoOperationFailedException {
        final FcrepoResponse response = clientBuilder.build().head(uri).perform();
        return response.getStatusCode() == 200;
    }

    private boolean resourceLinksTo(final URI linkFrom, final URI linkTo) throws FcrepoOperationFailedException {
        final FcrepoResponse response = clientBuilder.build().get(linkFrom).perform();
        final Model model = createDefaultModel().read(response.getBody(), null, "text/turtle");
        return model.contains(createResource(linkFrom.toString()), null, createResource(linkTo.toString()));
    }

    @Override
    protected Logger logger() {
        return getLogger(ImporterIT.class);
    }

}