package de.fhg.iais.cortex.rest.resources;

/******************************************************************************
 * Copyright 2011 (c) Fraunhofer IAIS Netmedia  http://www.iais.fraunhofer.de *
 * ************************************************************************** *
 * Licensed 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.                                             *
 ******************************************************************************/

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.stub;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.Mockito;

import de.fhg.iais.cortex.model.aip.AipObject;
import de.fhg.iais.cortex.model.aip.generated.Cortex;
import de.fhg.iais.cortex.rest.server.types.RestErrorType;
import de.fhg.iais.cortex.search.SolrFields;
import de.fhg.iais.cortex.search.types.Facet;
import de.fhg.iais.cortex.search.types.FacetValue;
import de.fhg.iais.cortex.search.types.Query;
import de.fhg.iais.cortex.search.types.SearchResults;
import de.fhg.iais.cortex.services.IAccessService;
import de.fhg.iais.cortex.services.ISearchService;
import de.fhg.iais.cortex.services.authentication.RoleProvider;
import de.fhg.iais.cortex.services.authentication.types.Role;
import de.fhg.iais.cortex.services.authentication.types.Role.Profile;
import de.fhg.iais.cortex.services.hierarchy.HierarchyNode;
import de.fhg.iais.cortex.services.hierarchy.HierarchyNodeList;
import de.fhg.iais.cortex.services.hierarchy.IHierarchyService;
import de.fhg.iais.cortex.storage.Binary;
import de.fhg.iais.cortex.storage.Component;
import de.fhg.iais.cortex.storage.exception.EmptyItemIdException;
import de.fhg.iais.cortex.storage.exception.ItemNotFoundException;

public class ItemsResourceTest {

    private static final String INFO_OBJECT2 = "<infoobject><name>Test2</name></infoobject>";
    private static final String BLACKLIST_ID = "0001000";
    private ItemsResource accessResource;

    private IAccessService mockAccessService;
    private ISearchService mockSearchService;
    private IHierarchyService mockHierarchyService;
    private RoleProvider mockRoleProvider;

    private AipObject aip;
    private Cortex cortex;

    List<HierarchyNode> parents;
    HierarchyNodeList children;

    @Before
    public void setUp() throws Exception {
        createMocks();

        this.accessResource = new ItemsResource(this.mockAccessService, this.mockHierarchyService, this.mockSearchService, this.mockRoleProvider, BLACKLIST_ID);
    }

    @SuppressWarnings("unchecked")
    public void createMocks() {
        Component mockComponent = mock(Component.class);
        stub(mockComponent.getValue()).toReturn(INFO_OBJECT2);

        Component mockComponent3 = mock(Component.class);
        stub(mockComponent3.getValue()).toReturn("rdf");

        this.mockAccessService = mock(IAccessService.class);
        stub(this.mockAccessService.getComponent("2", "view")).toReturn(mockComponent);
        stub(this.mockAccessService.getComponent("4", "indexing-profile")).toReturn(mockComponent3);
        stub(this.mockAccessService.getComponentAsJaxbObject("not there", "blah")).toThrow(
            new ItemNotFoundException("Item: not there / blah.xml not found: blah not found"));
        stub(this.mockAccessService.getAllComponents()).toReturn(Components.getComponentsAsStringList());
        stub(this.mockAccessService.getBinary("1", "test.txt")).toReturn(
            new Binary(new ByteArrayInputStream(("TestBinary").getBytes()), ("TestBinary").getBytes().length));
        stub(this.mockAccessService.getBinary("2", "fail.xml")).toThrow(new ItemNotFoundException("Item: 2 / fail.xml not found: Binary " + "not found"));

        this.cortex = new Cortex();
        this.aip = new AipObject(this.cortex);
        stub(this.mockAccessService.getAip("5")).toReturn(this.aip);
        stub(this.mockAccessService.getAip("6")).toThrow(new ItemNotFoundException("Item '6' not found."));
        stub(this.mockAccessService.getAip("7")).toThrow(new EmptyItemIdException("I am an error!"));
        stub(this.mockAccessService.getAip("not there")).toReturn(new AipObject());

        this.mockHierarchyService = mock(IHierarchyService.class);

        this.parents = new ArrayList<HierarchyNode>();
        this.children = new HierarchyNodeList();

        stub(this.mockHierarchyService.getParents("5")).toReturn(this.parents);
        stub(this.mockHierarchyService.getChildren(Matchers.eq("5"), Matchers.anyInt(), Matchers.anyInt())).toReturn(this.children);
        this.mockRoleProvider = mock(RoleProvider.class);
        stub(this.mockRoleProvider.get()).toReturn(new Role());

        this.mockSearchService = mock(ISearchService.class);

        Mockito.when(this.mockSearchService.searchFaceted(Matchers.any(Query.class))).thenReturn(new SearchResults());
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSecurityCheck() {
        HttpHeaders headers = mock(HttpHeaders.class);
        HttpServletRequest request = mock(HttpServletRequest.class);

        Role roleRestricted = new Role();
        roleRestricted.setProfile(Profile.RESTRICTED_READ);

        Role roleRead = new Role();
        roleRead.setProfile(Profile.READ);

        Role roleFull = new Role();
        roleFull.setProfile(Profile.FULL);

        this.mockRoleProvider = mock(RoleProvider.class);

        Facet providerBlacklistIdFacet = new Facet(SolrFields.PROVIDER_ID);
        providerBlacklistIdFacet.addFacetValue(new FacetValue(BLACKLIST_ID, 1));

        SearchResults resultBlacklistId = new SearchResults();
        resultBlacklistId.setFacets(new Facet[] {
            providerBlacklistIdFacet
        });

        Facet providerOtherIdFacet = new Facet(SolrFields.PROVIDER_ID);
        providerOtherIdFacet.addFacetValue(new FacetValue("0002000", 1));

        SearchResults resultOtherId = new SearchResults();
        resultOtherId.setFacets(new Facet[] {
            providerOtherIdFacet
        });

        Mockito
            .when(this.mockSearchService.searchFaceted(Matchers.any(Query.class)))
            .thenReturn(resultBlacklistId)
            .thenReturn(resultOtherId)
            .thenReturn(resultBlacklistId)
            .thenReturn(resultOtherId);

        this.accessResource = new ItemsResource(this.mockAccessService, this.mockHierarchyService, this.mockSearchService, this.mockRoleProvider, BLACKLIST_ID);

        // Check access for blacklisted providers and security level lesser than FULL
        stub(this.mockRoleProvider.get()).toReturn(roleRestricted).toReturn(roleRead);

        Response response = this.accessResource.getItems(headers, "5");
        Assert.assertTrue(response.getStatus() == 403);

        response = this.accessResource.getItems(headers, "5");
        Assert.assertTrue(response.getStatus() == 200);

        response = this.accessResource.getItems(headers, "5");
        Assert.assertTrue(response.getStatus() == 200);

        response = this.accessResource.getItems(headers, "5");
        Assert.assertTrue(response.getStatus() == 200);

        // Check blacklisted provider but full security level
        stub(this.mockRoleProvider.get()).toReturn(roleFull);

        Mockito
            .when(this.mockSearchService.searchFaceted(Matchers.any(Query.class)))
            .thenReturn(resultBlacklistId)
            .thenReturn(resultOtherId)
            .thenReturn(resultBlacklistId)
            .thenReturn(resultOtherId);

        response = this.accessResource.getItems(headers, "5");
        Assert.assertTrue(response.getStatus() == 200);

        response = this.accessResource.getItems(headers, "5");
        Assert.assertTrue(response.getStatus() == 200);

        response = this.accessResource.getItems(headers, "5");
        Assert.assertTrue(response.getStatus() == 200);

        response = this.accessResource.getItems(headers, "5");
        Assert.assertTrue(response.getStatus() == 200);
    }

    @Test
    public void testGet() {
        HttpHeaders headers = mock(HttpHeaders.class);

        Response response = this.accessResource.getItems(headers, "5");

        Assert.assertEquals(this.aip.getAip(), response.getEntity());

        response = this.accessResource.getItems(headers, "6");

        RestErrorType expectedError = new RestErrorType("ItemNotFoundException", "Item '6' not found.", "");
        RestErrorType actualError = (RestErrorType) response.getEntity();

        Assert.assertEquals(404, response.getStatus());
        Assert.assertEquals(expectedError, actualError);

        response = this.accessResource.getItems(headers, "7");

        expectedError = new RestErrorType("EmptyItemIdException", "I am an error!", "");
        actualError = (RestErrorType) response.getEntity();

        Assert.assertEquals(500, response.getStatus());
        Assert.assertEquals(expectedError, actualError);

    }

    @Test
    public void testGetAip() {
        HttpHeaders headers = mock(HttpHeaders.class);

        Response response = this.accessResource.getAip(headers, "5");

        Assert.assertEquals(this.aip.getAip(), response.getEntity());
        response = this.accessResource.getAip(headers, "6");

        RestErrorType expectedError = new RestErrorType("ItemNotFoundException", "Item '6' not found.", "");
        RestErrorType actualError = (RestErrorType) response.getEntity();

        Assert.assertEquals(404, response.getStatus());
        Assert.assertEquals(expectedError, actualError);

        response = this.accessResource.getAip(headers, "7");

        expectedError = new RestErrorType("EmptyItemIdException", "I am an error!", "");
        actualError = (RestErrorType) response.getEntity();

        Assert.assertEquals(500, response.getStatus());
        Assert.assertTrue(expectedError.equals(actualError));

    }

    @Test
    public void testGetParents() {
        HttpHeaders headers = mock(HttpHeaders.class);
        HttpServletRequest request = mock(HttpServletRequest.class);

        Response response = this.accessResource.getItemsParents(headers, "5");

        Assert.assertEquals(this.parents, response.getEntity());
    }

    @Test
    public void testGetChildren() {
        HttpHeaders headers = mock(HttpHeaders.class);
        HttpServletRequest request = mock(HttpServletRequest.class);

        Response response = this.accessResource.getItemsChildren(headers, "5", 1, 2);

        Assert.assertEquals(this.children, response.getEntity());
    }

    @Test
    public void testGetComponent() {
        HttpServletRequest request = mock(HttpServletRequest.class);

        Response response = this.accessResource.getItemsComponent("5", Components.VIEW.getComponentName());

        Mockito.verify(this.mockAccessService).getComponentAsJaxbObject("5", Components.VIEW.getComponentName());

        response = this.accessResource.getItemsComponent("5", "doesNotExist");

        RestErrorType expectedError = new RestErrorType("ItemNotFoundException", "Component 'doesNotExist' of item '5' not found.", "");
        RestErrorType actualError = (RestErrorType) response.getEntity();

        Assert.assertEquals(404, response.getStatus());
        Assert.assertEquals(expectedError, actualError);

        stub(this.mockAccessService.getComponentAsJaxbObject("5", Components.BINARIES.getComponentName())).toThrow(
            new EmptyItemIdException("Item id '5' does not exist!"));

        response = this.accessResource.getItemsComponent("5", Components.BINARIES.getComponentName());

        expectedError = new RestErrorType("EmptyItemIdException", "Item id '5' does not exist!", "");
        actualError = (RestErrorType) response.getEntity();

        Assert.assertEquals(500, response.getStatus());
        Assert.assertEquals(expectedError, actualError);
    }
}
