/* Copyright (c) 2016 Boundless and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/edl-v10.html
 *
 * Contributors:
 * Erik Merkle (Boundless) - initial implementation
 */
package org.locationtech.geogig.web;

import static org.locationtech.geogig.repository.Hints.REPOSITORY_NAME;
import static org.locationtech.geogig.repository.Hints.REPOSITORY_URL;
import static org.locationtech.geogig.web.InitRequestHandler.DB_HOST;
import static org.locationtech.geogig.web.InitRequestHandler.DB_NAME;
import static org.locationtech.geogig.web.InitRequestHandler.DB_PASSWORD;
import static org.locationtech.geogig.web.InitRequestHandler.DB_PORT;
import static org.locationtech.geogig.web.InitRequestHandler.DB_SCHEMA;
import static org.locationtech.geogig.web.InitRequestHandler.DB_USER;
import static org.locationtech.geogig.web.InitRequestHandler.DIR_PARENT_DIR;
import static org.locationtech.geogig.web.InitRequestHandler.REPO_ATTR;
import static org.restlet.data.MediaType.APPLICATION_JSON;
import static org.restlet.data.MediaType.APPLICATION_WWW_FORM;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;

import javax.json.Json;
import javax.json.JsonObject;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.locationtech.geogig.repository.Hints;
import org.locationtech.geogig.repository.RepositoryConnectionException;
import org.locationtech.geogig.rest.RestletException;
import org.restlet.data.Method;
import org.restlet.data.Request;
import org.restlet.resource.Representation;
import org.restlet.resource.StringRepresentation;

import com.google.common.base.Optional;

/**
 *
 */
public class InitRequestHandlerTest {

    @Rule
    public TemporaryFolder repoFolder = new TemporaryFolder();

    private final InitRequestHandler initRequestHandler = new InitRequestHandler();

    private Request buildRequest(Representation entity) {
        Request request = new Request(Method.PUT, "fake uri", entity);
        request.getAttributes().put(REPO_ATTR, "testRepo");
        return request;
    }

    private void assertRepositoryName(Hints hints) {
        Assert.assertTrue("Expected a Hints object to be created", hints != null);
        Assert.assertEquals("Incorrect Repository Name", "testRepo",
                hints.get(REPOSITORY_NAME).get());
    }

    @Test(expected = RestletException.class)
    public void testMissingRepositoryName() throws URISyntaxException, IOException, UnsupportedEncodingException,
            RepositoryConnectionException {
        Request request = new Request(Method.PUT, "fake uri");
        // build the Hints without a repository name getting in the attributes
        // should throw an Exception
        Hints hints = initRequestHandler.createHintsFromRequest(request);
    }

    @Test
    public void testCreateGeoGIG_RepositoryName() throws URISyntaxException, IOException, UnsupportedEncodingException,
            RepositoryConnectionException {
        // build an Init request with only a repository name
        Request request = buildRequest(null);
        // create the Hints from the request
        Hints hints = initRequestHandler.createHintsFromRequest(request);
        // assert the correct Repository Name is in the Hints
        assertRepositoryName(hints);
        // REPOSITORY_URI should NOT be set in the Hints, it should be generated by the Repository
        // Manager
        Optional<Serializable> repoURL = hints.get(REPOSITORY_URL);
        Assert.assertFalse("Expected REPOSIOTRY_URL to be ABSENT", repoURL.isPresent());
    }

    @Test
    public void testJSONMediaType() throws URISyntaxException, IOException, UnsupportedEncodingException,
            RepositoryConnectionException {
        // temp directory for the repo
        File repoDir = repoFolder.getRoot().getAbsoluteFile().getCanonicalFile();
        // populate a JSON payload for a Directory repo
        JsonObject jsonObject = Json.createObjectBuilder()
                .add(DIR_PARENT_DIR, repoDir.getCanonicalPath())
                .build();
        StringRepresentation entity = new StringRepresentation(jsonObject.toString(), APPLICATION_JSON);
        // ensure the Content-Type is JSON
        Assert.assertEquals("Bad MediaType", APPLICATION_JSON, entity.getMediaType());
        // build the request
        Request request = buildRequest(entity);
        // create the Hints from the Request
        Hints hints = initRequestHandler.createHintsFromRequest(request);
        // assert the correct Repository Name is in the Hints
        assertRepositoryName(hints);
        // REPOSITORY_URI should be set in the Hints
        Optional<Serializable> repoURL = hints.get(REPOSITORY_URL);
        Assert.assertTrue("Expected REPOSIOTRY_URL to be PRESENT", repoURL.isPresent());
        URI actual = URI.create(repoURL.get().toString());
        File actualParent = new File(actual).getParentFile();
        Assert.assertEquals("Repository Parent Directory does not match", repoDir, actualParent);
    }

    @Test
    public void testPGRepo() throws URISyntaxException, IOException, UnsupportedEncodingException,
            RepositoryConnectionException {
        // populate a JSON payload for a PG repo
        JsonObject jsonObject = Json.createObjectBuilder()
        // add the DB attributes with no defaults at a minimum
                .add(DB_NAME, "pgDatabaseName")
                .add(DB_PASSWORD, "fakePassword")
                .build();
        StringRepresentation entity = new StringRepresentation(jsonObject.toString(), APPLICATION_JSON);
        // ensure the Content-Type is JSON
        Assert.assertEquals("Bad MediaType", APPLICATION_JSON, entity.getMediaType());
        // build the Request
        Request request = buildRequest(entity);
        // create the Hints from the Request
        Hints hints = initRequestHandler.createHintsFromRequest(request);
        // assert the correct Repository Name is in the Hints
        assertRepositoryName(hints);
        // REPOSITORY_URI should be set in the Hints
        Optional<Serializable> repoURL = hints.get(REPOSITORY_URL);
        Assert.assertTrue("Expected REPOSIOTRY_URL to be PRESENT", repoURL.isPresent());
        URI actual = URI.create(repoURL.get().toString());
        Assert.assertEquals("Unexpected URI Scheme", "postgresql", actual.getScheme());
        Assert.assertEquals("Unexpected URI Host", "localhost", actual.getHost());
        Assert.assertEquals("Unexpected URI Port", 5432, actual.getPort());
        Assert.assertEquals("Unexpected URI Path", "/pgDatabaseName/public/testRepo", actual.getPath());
        Assert.assertEquals("Unexpected URI Query", "user=postgres&password=fakePassword", actual.getQuery());
    }

    @Test
    public void testAllPGParameters() throws URISyntaxException, IOException, UnsupportedEncodingException,
            RepositoryConnectionException {
        // populate a JSON payload for a PG repo
        JsonObject jsonObject = Json.createObjectBuilder()
        // add the DB attributes with no defaults at a minimum
                .add(DB_NAME, "pgDatabaseName")
                .add(DB_PASSWORD, "fakePassword")
                .add(DB_SCHEMA, "fakeSchema")
                .add(DB_USER, "fakeUser")
                .add(DB_HOST, "fakeHost")
                .add(DB_PORT, "8899")
                .build();
        StringRepresentation entity = new StringRepresentation(jsonObject.toString(), APPLICATION_JSON);
        // ensure the Content-Type is JSON
        Assert.assertEquals("Bad MediaType", APPLICATION_JSON, entity.getMediaType());
        // build the Request
        Request request = buildRequest(entity);
        // create the Hints from the Request
        Hints hints = initRequestHandler.createHintsFromRequest(request);
        // assert the correct Repository Name is in the Hints
        assertRepositoryName(hints);
        // REPOSITORY_URI should be set in the Hints
        Optional<Serializable> repoURL = hints.get(REPOSITORY_URL);
        Assert.assertTrue("Expected REPOSIOTRY_URL to be PRESENT", repoURL.isPresent());
        URI actual = URI.create(repoURL.get().toString());
        Assert.assertEquals("Unexpected URI Scheme", "postgresql", actual.getScheme());
        // assert the attributes we built into the JSON request
        Assert.assertEquals("Unexpected URI Scheme", "postgresql", actual.getScheme());
        Assert.assertEquals("Unexpected URI Host", "fakeHost", actual.getHost());
        Assert.assertEquals("Unexpected URI Port", 8899, actual.getPort());
        Assert.assertEquals("Unexpected URI Path", "/pgDatabaseName/fakeSchema/testRepo", actual.getPath());
        Assert.assertEquals("Unexpected URI Query", "user=fakeUser&password=fakePassword", actual.getQuery());
    }

    @Test
    public void testPGRepoBadPort() throws URISyntaxException, IOException, UnsupportedEncodingException,
            RepositoryConnectionException {
        // populate a JSON payload for a PG repo
        JsonObject jsonObject = Json.createObjectBuilder()
        // add the DB attributes with no defaults at a minimum
                .add(DB_NAME, "pgDatabaseName")
                .add(DB_PASSWORD, "fakePassword")
                // fill in junk for port
                .add(DB_PORT, "non-parsable integer")
                .build();
        StringRepresentation entity = new StringRepresentation(jsonObject.toString(), APPLICATION_JSON);
        // ensure the Content-Type is JSON
        Assert.assertEquals("Bad MediaType", APPLICATION_JSON, entity.getMediaType());
        // build the Request
        Request request = buildRequest(entity);
        // create the Hints from the Request
        Hints hints = initRequestHandler.createHintsFromRequest(request);
        // assert the correct Repository Name is in the Hints
        assertRepositoryName(hints);
        // REPOSITORY_URI should be set in the Hints
        Optional<Serializable> repoURL = hints.get(REPOSITORY_URL);
        Assert.assertTrue("Expected REPOSIOTRY_URL to be PRESENT", repoURL.isPresent());
        URI actual = URI.create(repoURL.get().toString());
        Assert.assertEquals("Unexpected URI Scheme", "postgresql", actual.getScheme());
        // default Postgres config
        Assert.assertEquals("Unexpected URI Scheme", "postgresql", actual.getScheme());
        Assert.assertEquals("Unexpected URI Host", "localhost", actual.getHost());
        Assert.assertEquals("Unexpected URI Port", 5432, actual.getPort());
        Assert.assertEquals("Unexpected URI Path", "/pgDatabaseName/public/testRepo", actual.getPath());
        Assert.assertEquals("Unexpected URI Query", "user=postgres&password=fakePassword", actual.getQuery());
    }

    @Test
    public void testURLEncodedForm() throws URISyntaxException, IOException, UnsupportedEncodingException,
            RepositoryConnectionException {
        // build an Init request with only a repository name
        Request request = buildRequest(null);
        // set the form encoded data
        StringBuilder data = new StringBuilder(128);
        data.append(DB_NAME).append("=").append("pgDatabaseName").append("&");
        data.append(DB_PASSWORD).append("=").append("fakePassword").append("&");
        data.append(DB_SCHEMA).append("=").append("fakeSchema").append("&");
        data.append(DB_USER).append("=").append("fakeUser").append("&");
        data.append(DB_HOST).append("=").append("fakeHost").append("&");
        data.append(DB_PORT).append("=").append("8899");
        request.setEntity(data.toString(), APPLICATION_WWW_FORM);
        // create the Hints from the Request
        Hints hints = initRequestHandler.createHintsFromRequest(request);
        // assert the correct Repository Name is in the Hints
        assertRepositoryName(hints);
        // REPOSITORY_URI should be set in the Hints
        Optional<Serializable> repoURL = hints.get(REPOSITORY_URL);
        Assert.assertTrue("Expected REPOSIOTRY_URL to be PRESENT", repoURL.isPresent());
        URI actual = URI.create(repoURL.get().toString());
        Assert.assertEquals("Unexpected URI Scheme", "postgresql", actual.getScheme());
        // default Postgres config
        // assert the attributes we built into the JSON request
        Assert.assertEquals("Unexpected URI Scheme", "postgresql", actual.getScheme());
        Assert.assertEquals("Unexpected URI Host", "fakeHost", actual.getHost());
        Assert.assertEquals("Unexpected URI Port", 8899, actual.getPort());
        Assert.assertEquals("Unexpected URI Path", "/pgDatabaseName/fakeSchema/testRepo", actual.getPath());
        Assert.assertEquals("Unexpected URI Query", "user=fakeUser&password=fakePassword", actual.getQuery());
    }
}
