/*
 * Copyright (C) 2001-2016 Food and Agriculture Organization of the
 * United Nations (FAO-UN), United Nations World Food Programme (WFP)
 * and United Nations Environment Programme (UNEP)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
 * Rome - Italy. email: geonetwork@osgeo.org
 */
package org.fao.geonet.api.records;

import static org.fao.geonet.kernel.mef.MEFLib.Version.Constants.MEF_V1_ACCEPT_TYPE;
import static org.fao.geonet.kernel.mef.MEFLib.Version.Constants.MEF_V2_ACCEPT_TYPE;
import static org.fao.geonet.schema.iso19139.ISO19139Namespaces.GCO;
import static org.fao.geonet.schema.iso19139.ISO19139Namespaces.GMD;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.imageio.ImageIO;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.fao.geonet.NodeInfo;
import org.fao.geonet.api.ApiParams;
import org.fao.geonet.api.records.model.related.RelatedItemType;
import org.fao.geonet.constants.Params;
import org.fao.geonet.domain.Metadata;
import org.fao.geonet.domain.MetadataType;
import org.fao.geonet.kernel.DataManager;
import org.fao.geonet.kernel.SchemaManager;
import org.fao.geonet.kernel.SpringLocalServiceInvoker;
import org.fao.geonet.kernel.UpdateDatestamp;
import org.fao.geonet.kernel.mef.MEFLibIntegrationTest;
import org.fao.geonet.lib.Lib;
import org.fao.geonet.repository.MetadataRepository;
import org.fao.geonet.repository.SourceRepository;
import org.fao.geonet.services.AbstractServiceIntegrationTest;
import org.fao.geonet.utils.Xml;
import org.jdom.Element;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.google.common.collect.Lists;

import jeeves.server.context.ServiceContext;


/**
 * Tests for class {@link MetadataApi}.
 *
 * @author juanluisrp
 **/
public class MetadataApiTest extends AbstractServiceIntegrationTest {
    @Autowired
    private WebApplicationContext wac;
    @Autowired
    private SchemaManager schemaManager;
    @Autowired
    private DataManager dataManager;
    @Autowired
    private SourceRepository sourceRepository;

    @PersistenceContext
    private EntityManager _entityManager;

    @Autowired private MetadataRepository metadataRepository;

    private String uuid;
    private int id;
    private ServiceContext context;


    @Before
    public void setUp() throws Exception {
        this.context = createServiceContext();
        createTestData();
    }

    private void createTestData() throws Exception {
        loginAsAdmin(context);

        final Element sampleMetadataXml = getSampleMetadataXml();
        this.uuid = UUID.randomUUID().toString();
        Xml.selectElement(sampleMetadataXml, "gmd:fileIdentifier/gco:CharacterString", Arrays.asList(GMD, GCO)).setText(this.uuid);

        String source = sourceRepository.findAll().get(0).getUuid();
        String schema = schemaManager.autodetectSchema(sampleMetadataXml);
        final Metadata metadata = (Metadata) new Metadata()
            .setDataAndFixCR(sampleMetadataXml)
            .setUuid(uuid);
        metadata.getDataInfo()
            .setRoot(sampleMetadataXml.getQualifiedName())
            .setSchemaId(schema)
            .setType(MetadataType.METADATA)
            .setPopularity(1000);
        metadata.getSourceInfo()
            .setOwner(1)
            .setSourceId(source);
        metadata.getHarvestInfo()
            .setHarvested(false);

        this.id = dataManager.insertMetadata(context, metadata, sampleMetadataXml, false, true, false, UpdateDatestamp.NO,
            false, false).getId();
    }


    @Test
    public void getNonExistentRecordRecord() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAnonymous();
        String nonExistentUuid = UUID.randomUUID().toString();

        List<String> contentTypeWithoutBodyList = Lists.newArrayList(
            MediaType.TEXT_HTML_VALUE,
            "application/pdf",
            "application/zip",
            MEF_V1_ACCEPT_TYPE,
            MEF_V2_ACCEPT_TYPE
        );

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid)
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            .andExpect(jsonPath("$.code").value(equalTo("resource_not_found")));

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid)
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(xpath("/apiError/code").string("resource_not_found"));


        for (String contentTypeWithoutBody : contentTypeWithoutBodyList) {
            mockMvc.perform(get("/srv/api/records/" + nonExistentUuid)
                .session(mockHttpSession)
                .accept(contentTypeWithoutBody))
                .andExpect(status().isNotFound())
                .andExpect(content().string(isEmptyOrNullString()));
        }
    }

    @Test
    public void getNonAllowedRecord() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAnonymous();

        List<String> contentTypeWithoutBodyList = Lists.newArrayList(
            MediaType.TEXT_HTML_VALUE,
            "application/pdf",
            "application/zip",
            MEF_V1_ACCEPT_TYPE,
            MEF_V2_ACCEPT_TYPE
        );


        mockMvc.perform(get("/srv/api/records/" + this.uuid)
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isForbidden())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            .andExpect(jsonPath("$.code").value(equalTo("forbidden")))
            .andExpect(jsonPath("$.message").value(equalTo(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)));

        mockMvc.perform(get("/srv/api/records/" + this.uuid)
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andExpect(status().isForbidden())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(xpath("/apiError/code").string(equalTo("forbidden")))
            .andExpect(xpath("/apiError/message").string(equalTo(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)));

        mockMvc.perform(get("/srv/api/records/" + this.uuid)
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XHTML_XML_VALUE))
            .andExpect(status().isForbidden())
            .andExpect(content().contentType(MediaType.APPLICATION_XHTML_XML_VALUE))
            .andExpect(xpath("/apiError/code").string(equalTo("forbidden")))
            .andExpect(xpath("/apiError/message").string(equalTo(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)));


        for (String contentTypeWihoutBody : contentTypeWithoutBodyList) {
            mockMvc.perform(get("/srv/api/records/" + this.uuid)
                .session(mockHttpSession)
                .accept(contentTypeWihoutBody))
                .andExpect(status().isForbidden())
                .andExpect(content().string(isEmptyOrNullString()));
        }
    }

    @Test
    public void getRecord() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAdmin();

        Map<String, String> contentTypes = new LinkedHashMap<>();
        contentTypes.put(MediaType.TEXT_HTML_VALUE, this.uuid + "/formatters/xsl-view");
        contentTypes.put(MediaType.APPLICATION_XHTML_XML_VALUE, this.uuid + "/formatters/xsl-view");
        contentTypes.put("application/pdf", this.uuid + "/formatters/xsl-view");
        contentTypes.put(MediaType.APPLICATION_XML_VALUE, this.uuid + "/formatters/xml");
        contentTypes.put(MediaType.APPLICATION_JSON_VALUE, this.uuid + "/formatters/xml");
        contentTypes.put("application/zip", this.uuid + "/formatters/zip");
        contentTypes.put(MEF_V1_ACCEPT_TYPE, this.uuid + "/formatters/zip");
        contentTypes.put(MEF_V2_ACCEPT_TYPE, this.uuid + "/formatters/zip");

        for(Map.Entry<String, String> entry : contentTypes.entrySet()) {
            mockMvc.perform(get("/srv/api/records/" + this.uuid)
                .session(mockHttpSession)
                .accept(entry.getKey()))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(forwardedUrl(entry.getValue()));
        }
    }

    @Test
    public void getRecordThruSpringLocalServiceInvoker() throws Exception {
        MockMvcBuilders.webAppContextSetup(this.wac).build();
        loginAsAdmin();
        SpringLocalServiceInvoker toTest = super._applicationContext.getBean(SpringLocalServiceInvoker.class);
        super._applicationContext.getBean(NodeInfo.class).setId("srv");
        toTest.init();

        Object resp = toTest.invoke("local://srv/api/records/" + uuid + "/formatters/xml");

        assertEquals("MD_Metadata", ((Element)resp).getName());
    }

    @Test
    public void getRecordAsXML() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAdmin();

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/xml")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "xml"))))
            .andExpect(content().string(containsString(this.uuid)))
            .andExpect(xpath("/MD_Metadata/fileIdentifier/CharacterString").string(this.uuid));

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/json")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "json"))))
            .andExpect(content().string(containsString(this.uuid)))
            .andExpect(jsonPath("$.['gmd:fileIdentifier'].['gco:CharacterString'].['#text']").value(this.uuid));
    }

    @Test
    public void getRecordAsXMLAddSchemaLocation() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAdmin();

        // Add Schema locations
        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/xml").param("addSchemaLocation", "true")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "xml"))))
            .andExpect(content().string(containsString(this.uuid)))
            .andExpect(content().string(containsString(".xsd")))
            .andExpect(xpath("/MD_Metadata/fileIdentifier/CharacterString").string(this.uuid));

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/xml").param("addSchemaLocation", "false")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "xml"))))
            .andExpect(content().string(containsString(this.uuid)))
            .andExpect(content().string(not(containsString(".xsd"))))
            .andExpect(xpath("/MD_Metadata/fileIdentifier/CharacterString").string(this.uuid));
    }

    @Test
    public void getRecordAsXMLIncreasePopularity() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAdmin();
        int popularity = metadataRepository.findOne(this.id).getDataInfo().getPopularity();

        // Add Schema locations
        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/xml").param("increasePopularity", "true")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "xml"))))
            .andExpect(content().string(containsString(this.uuid)))
            .andExpect(xpath("/MD_Metadata/fileIdentifier/CharacterString").string(this.uuid));

        // Seem some issue with the transaction in the tests, requires to use explicitly the entity manager.
        // In the application looks working fine with the @Transactional annotation in MetadataRepository.incrementPopularity
        _entityManager.flush();
        _entityManager.clear();

        int newPopularity = metadataRepository.findOneByUuid(this.uuid).getDataInfo().getPopularity();
        Assert.assertThat("Popularity has not been incremented by one", newPopularity, equalTo(popularity + 1));

        popularity = newPopularity;

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/xml").param("increasePopularity", "false")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "xml"))))
            .andExpect(content().string(containsString(this.uuid)))
            .andExpect(xpath("/MD_Metadata/fileIdentifier/CharacterString").string(this.uuid));

        // Seem some issue with the transaction in the tests, requires to use explicitly the entity manager.
        // In the application looks working fine with the @Transactional annotation in MetadataRepository.incrementPopularity
        _entityManager.flush();
        _entityManager.clear();

        newPopularity = metadataRepository.findOneByUuid(this.uuid).getDataInfo().getPopularity();
        Assert.assertThat("Popularity has changed", newPopularity, equalTo(popularity));
    }

    @Test
    public void getNonAllowedRecordAsXml() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAnonymous();

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/json")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isForbidden())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            .andExpect(jsonPath("$.code").value(equalTo("forbidden")))
            .andExpect(jsonPath("$.message").value(equalTo(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)));

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/xml")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andExpect(status().isForbidden())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(xpath("/apiError/code").string(equalTo("forbidden")))
            .andExpect(xpath("/apiError/message").string(equalTo(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)));
    }

    @Test
    public void getNonExistentRecordAsXml() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAnonymous();
        String nonExistentUuid = UUID.randomUUID().toString();

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid + "/formatters/json")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            .andExpect(jsonPath("$.code").value(equalTo("resource_not_found")));

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid + "/formatters/xml")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(xpath("/apiError/code").string("resource_not_found"));
    }

    @Test
    public void getRecordAsZip() throws Exception {

        final String zipMagicNumber = "PK\u0003\u0004";

        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAdmin();

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept("application/zip"))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MEF_V2_ACCEPT_TYPE))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "zip"))))
            .andExpect(content().string(startsWith(zipMagicNumber)));

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept(MEF_V1_ACCEPT_TYPE))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MEF_V1_ACCEPT_TYPE))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "zip"))))
            .andExpect(content().string(startsWith(zipMagicNumber)));

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept(MEF_V1_ACCEPT_TYPE))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MEF_V1_ACCEPT_TYPE))
            .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION,
                equalTo(String.format("inline; filename=\"%s.%s\"", this.uuid, "zip"))))
            .andExpect(content().string(startsWith(zipMagicNumber)));

    }

    @Test
    public void getNonAllowedRecordAsZip() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAnonymous();

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept("application/zip"))
            .andExpect(status().isForbidden());

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept(MEF_V1_ACCEPT_TYPE))
            .andExpect(status().isForbidden());

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept(MEF_V2_ACCEPT_TYPE))
            .andExpect(status().isForbidden());
    }

    @Test
    public void getNonExistentRecordAsZip() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAnonymous();
        String nonExistentUuid = UUID.randomUUID().toString();

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept("application/zip"))
            .andExpect(status().isNotFound())
            .andExpect(header().doesNotExist(HttpHeaders.CONTENT_TYPE))
            .andExpect(content().string(isEmptyOrNullString()));

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept(MEF_V1_ACCEPT_TYPE))
            .andExpect(status().isNotFound())
            .andExpect(header().doesNotExist(HttpHeaders.CONTENT_TYPE))
            .andExpect(content().string(isEmptyOrNullString()));

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid + "/formatters/zip")
            .session(mockHttpSession)
            .accept(MEF_V2_ACCEPT_TYPE))
            .andExpect(status().isNotFound())
            .andExpect(header().doesNotExist(HttpHeaders.CONTENT_TYPE))
            .andExpect(content().string(isEmptyOrNullString()));
    }


    @Test
    public void getRelatedNonExistent() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAnonymous();
        String nonExistentUuid = UUID.randomUUID().toString();

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid + "/related")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            .andExpect(jsonPath("$.code").value(equalTo("resource_not_found")));

        mockMvc.perform(get("/srv/api/records/" + nonExistentUuid + "/related")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(xpath("/apiError/code").string("resource_not_found"));

    }

    @Test
    public void getRelatedNonAllowed() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAnonymous();

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/related")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isForbidden())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            .andExpect(jsonPath("$.code").value(equalTo("forbidden")))
            .andExpect(jsonPath("$.message").value(equalTo(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)));

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/related")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andExpect(status().isForbidden())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(xpath("/apiError/code").string(equalTo("forbidden")))
            .andExpect(xpath("/apiError/message").string(equalTo(ApiParams.API_RESPONSE_NOT_ALLOWED_CAN_VIEW)));
    }

    @Test
    public void getRelated() throws Exception {
        MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        MockHttpSession mockHttpSession = loginAsAdmin();
        addThumbnails(this.context);

        final MEFLibIntegrationTest.ImportMetadata importMetadata = new MEFLibIntegrationTest.ImportMetadata(this, context);
        importMetadata.getMefFilesToLoad().add("/org/fao/geonet/api/records/samples/mef2-related.zip");
        importMetadata.invoke();
        final String MAIN_UUID = "655bb8a1-0324-470f-8dff-bb64e849291c";
        final String DATASET_UUID = "842f9143-fd7d-452c-96b4-425ca1281642";


        mockMvc.perform(get("/srv/api/records/" + MAIN_UUID + "/related")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING));

        mockMvc.perform(get("/srv/api/records/" + MAIN_UUID + "/related")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(xpath("/related/onlines").exists());

        mockMvc.perform(get("/srv/api/records/" + this.uuid + "/related")
            .param("type", RelatedItemType.thumbnails.toString())
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            //.andExpect(jsonPath("$.children").isNotEmpty())
            .andExpect(jsonPath("$." + RelatedItemType.thumbnails, notNullValue()));

        // Request each type
        for (RelatedItemType type : RelatedItemType.values()) {
            if (type == RelatedItemType.hassources ||
                type == RelatedItemType.related ||
                type == RelatedItemType.hasfeaturecats ||
                type == RelatedItemType.brothersAndSisters ||
                type == RelatedItemType.thumbnails) {
                // TODO modify mef2-related.zip test metadata to contain a valid hassources value
                continue;
            }
            String uuidToTest = MAIN_UUID;
            if (type == RelatedItemType.datasets) {
                uuidToTest = DATASET_UUID;
            }
            mockMvc.perform(get("/srv/api/records/" + uuidToTest + "/related")
                .param("type", type.toString())
                .session(mockHttpSession)
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
                .andExpect(jsonPath("$." + type).isNotEmpty())
                .andExpect(jsonPath("$").value(hasKey(type.toString())));

            mockMvc.perform(get("/srv/api/records/" + uuidToTest + "/related")
                .param("type", type.toString())
                .session(mockHttpSession)
                .accept(MediaType.APPLICATION_XML))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_XML))
                .andExpect(xpath("/related/" + type.toString() + "/item").exists());
        }

        // Check start and row parameters


        mockMvc.perform(get("/srv/api/records/" + MAIN_UUID + "/related")
            .param("type", RelatedItemType.children.toString())
            .param("start", "2")
            .param("rows", "1")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_JSON))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(API_JSON_EXPECTED_ENCODING))
            .andExpect(jsonPath("$." + RelatedItemType.children).isArray())
            .andExpect(jsonPath("$." + RelatedItemType.children, hasSize(1)));

        mockMvc.perform(get("/srv/api/records/" + MAIN_UUID + "/related")
            .param("type", RelatedItemType.children.toString())
            .param("start", "2")
            .param("rows", "1")
            .session(mockHttpSession)
            .accept(MediaType.APPLICATION_XML))
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_XML))
            .andExpect(xpath("/related/" + RelatedItemType.children + "/item").exists())
            .andExpect(xpath("/related/" + RelatedItemType.children + "/item").nodeCount(1));

    }

    private void addThumbnails(ServiceContext context) throws Exception {
        Path mdPublicDataDir = Lib.resource.getDir(context, Params.Access.PUBLIC, id);
        Path mdPrivateDataDir = Lib.resource.getDir(context, Params.Access.PRIVATE, id);
        final Path smallImage = mdPublicDataDir.resolve("small.gif");
        final Path largeImage = mdPublicDataDir.resolve("large.gif");
        createImage("gif", smallImage);
        createImage("gif", largeImage);

        final Path privateImage = mdPrivateDataDir.resolve("privateFile.gif");
        createImage("gif", privateImage);

        dataManager.setThumbnail(context, Integer.toString(this.id), true,
            smallImage.toAbsolutePath().normalize().toString(), false);
        dataManager.setThumbnail(context, Integer.toString(this.id), false,
            largeImage.toAbsolutePath().normalize().toString(), false);
    }

    private String createImage(String format, Path outFile) throws IOException {

        BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D g2d = image.createGraphics();
        g2d.drawRect(1, 1, 5, 5);
        g2d.dispose();
        Files.createDirectories(outFile.getParent());
        Files.createFile(outFile);
        try (OutputStream out = Files.newOutputStream(outFile)) {
            final boolean writerWasFound = ImageIO.write(image, format, out);
            assertTrue(writerWasFound);
        }

        return outFile.toAbsolutePath().normalize().toString();
    }
}
