/*******************************************************************************
 *
 *    Copyright 2019 Adobe. All rights reserved.
 *    This file is licensed 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 REPRESENTATIONS
 *    OF ANY KIND, either express or implied. See the License for the specific language
 *    governing permissions and limitations under the License.
 *
 ******************************************************************************/

package com.adobe.cq.commerce.graphql.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;

import javax.jcr.RepositoryException;
import javax.servlet.RequestDispatcher;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.wrappers.ModifiableValueMapDecorator;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import com.adobe.cq.commerce.api.conf.CommerceBasePathsService;
import com.adobe.granite.omnisearch.spi.core.OmniSearchHandler;
import com.day.cq.commons.feed.StringResponseWrapper;
import com.day.cq.search.PredicateGroup;
import com.day.cq.search.result.SearchResult;
import com.day.cq.tagging.TagManager;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.core.contentfinder.Hit;
import com.day.cq.wcm.core.contentfinder.ViewQuery;

import static com.adobe.cq.commerce.graphql.resource.GraphqlQueryLanguageProvider.CATEGORY_ID_PARAMETER;
import static com.adobe.cq.commerce.graphql.resource.GraphqlQueryLanguageProvider.CATEGORY_PATH_PARAMETER;
import static com.adobe.cq.commerce.graphql.search.CatalogSearchSupport.PN_CATALOG_PATH;

public class GraphqlProductViewHandlerTest {

    private static final String LINE_SEPARATOR = System.lineSeparator();

    SlingHttpServletRequest servletRequest;
    ResourceResolver resourceResolver;
    TagManager tagManager;
    CommerceBasePathsService commerceBasePathsService;
    RequestPathInfo requestPathInfo;
    OmniSearchHandler omniSearchHandler;
    SearchResult searchResult;
    GraphqlProductViewHandler viewHandler;

    @Before
    public void setUp() {
        servletRequest = Mockito.mock(SlingHttpServletRequest.class);
        resourceResolver = Mockito.mock(ResourceResolver.class);
        Mockito.when(servletRequest.getResourceResolver()).thenReturn(resourceResolver);

        tagManager = Mockito.mock(TagManager.class);
        Mockito.when(resourceResolver.adaptTo(TagManager.class)).thenReturn(tagManager);

        commerceBasePathsService = Mockito.mock(CommerceBasePathsService.class);
        Mockito.when(resourceResolver.adaptTo(CommerceBasePathsService.class)).thenReturn(commerceBasePathsService);
        Mockito.when(commerceBasePathsService.getProductsBasePath()).thenReturn("/var/commerce/products");

        requestPathInfo = Mockito.mock(RequestPathInfo.class);
        Mockito.when(servletRequest.getRequestPathInfo()).thenReturn(requestPathInfo);
        Mockito.when(requestPathInfo.getSuffix()).thenReturn("/var/commerce/products");

        omniSearchHandler = Mockito.mock(OmniSearchHandler.class);
        searchResult = Mockito.mock(SearchResult.class);

        viewHandler = new GraphqlProductViewHandler();
        viewHandler.omniSearchHandler = omniSearchHandler;
        Mockito.when(omniSearchHandler.getResults(resourceResolver, null, 0, 0)).thenReturn(searchResult);
    }

    @Test
    public void testCreateTagSearchQuery() throws RepositoryException {
        ViewQuery viewQuery = viewHandler.createQuery(servletRequest, null, "tag:me");
        PredicateGroup predicateGroup = ((GraphqlProductViewHandler.GQLViewQuery) viewQuery).predicateGroup;
        Assert.assertNotNull(predicateGroup.getByName("1_tagsearch"));
    }

    @Test
    public void testCreateTextSearchQuery() throws RepositoryException {
        ViewQuery viewQuery = viewHandler.createQuery(servletRequest, null, "query=trail");
        PredicateGroup predicateGroup = ((GraphqlProductViewHandler.GQLViewQuery) viewQuery).predicateGroup;
        Assert.assertTrue(((PredicateGroup) predicateGroup.get(0)).get(0).getType().equals("fulltext"));
    }

    @Test
    public void testCreateEmptySearchQueryWithLimit() throws RepositoryException {
        Mockito.when(servletRequest.getParameter("limit")).thenReturn("0..20");
        ViewQuery viewQuery = viewHandler.createQuery(servletRequest, null, "");
        PredicateGroup predicateGroup = ((GraphqlProductViewHandler.GQLViewQuery) viewQuery).predicateGroup;
        Assert.assertEquals("0", predicateGroup.get("offset"));
        Assert.assertEquals("20", predicateGroup.get("limit"));
    }

    @Test
    public void testCreateRatingSearchQuery() throws RepositoryException {
        ViewQuery viewQuery = viewHandler.createQuery(servletRequest, null, "rating:5");
        PredicateGroup predicateGroup = ((GraphqlProductViewHandler.GQLViewQuery) viewQuery).predicateGroup;
        Assert.assertEquals(predicateGroup.getByName("1_property").get("property"), "rating");
    }

    @Test
    public void testCategoryConstraintSearchQuery() throws RepositoryException {
        Mockito.when(servletRequest.getHeader("Referer")).thenReturn("editor.html/test/page.html");
        PageManager pageManager = Mockito.mock(PageManager.class);
        Mockito.when(resourceResolver.adaptTo(PageManager.class)).thenReturn(pageManager);
        Page page = Mockito.mock(Page.class);
        Mockito.when(pageManager.getContainingPage("/test/page")).thenReturn(page);
        Resource contentResource = Mockito.mock(Resource.class);
        Mockito.when(page.getContentResource()).thenReturn(contentResource);
        ModifiableValueMapDecorator valueMap = new ModifiableValueMapDecorator(new HashMap<>());
        Mockito.when(contentResource.getValueMap()).thenReturn(valueMap);
        valueMap.put(PN_CATALOG_PATH, "/catalog/path");
        Resource resource = Mockito.mock(Resource.class);
        Mockito.when(resourceResolver.getResource("/catalog/path")).thenReturn(resource);
        ModifiableValueMapDecorator properties = new ModifiableValueMapDecorator(new HashMap<>());
        Mockito.when(resource.getValueMap()).thenReturn(properties);
        properties.put("cq:commerceType", "category");
        properties.put("cifId", "1");

        ViewQuery viewQuery = viewHandler.createQuery(servletRequest, null, "query=trail");
        PredicateGroup predicateGroup = ((GraphqlProductViewHandler.GQLViewQuery) viewQuery).predicateGroup;
        Assert.assertNotNull(predicateGroup.getByName("3_" + CATEGORY_ID_PARAMETER));
        Assert.assertEquals(predicateGroup.getByName("3_" + CATEGORY_ID_PARAMETER).get(CATEGORY_ID_PARAMETER), "1");
        Assert.assertNotNull(predicateGroup.getByName("4_" + CATEGORY_PATH_PARAMETER));
        Assert.assertEquals(predicateGroup.getByName("4_" + CATEGORY_PATH_PARAMETER).get(CATEGORY_PATH_PARAMETER), "/catalog/path");
    }

    @Test
    public void testGenerateHtmlOutput() throws Exception {
        SlingHttpServletResponse servletResponse = Mockito.mock(SlingHttpServletResponse.class);
        Collection<Hit> hits = new ArrayList<>();
        Hit hit = new Hit();
        hit.set("path", "/hit/path1");

        Hit hit2 = new Hit();
        hit2.set("path", "/hit/path2");
        hit2.set("resource", Mockito.mock(Resource.class));

        RequestDispatcher requestDispatcher = Mockito.mock(RequestDispatcher.class);
        Mockito.when(servletRequest.getRequestDispatcher(Mockito.any(Resource.class), Mockito.any())).thenReturn(requestDispatcher);
        Mockito.doAnswer(invocation -> {
            StringResponseWrapper response = invocation.getArgumentAt(1, StringResponseWrapper.class);
            response.getWriter().println("Hit");
            return null;
        }).when(requestDispatcher).include(Mockito.any(), Mockito.any(StringResponseWrapper.class));

        // no hit, no itemResourceType
        String output = viewHandler.generateHtmlOutput(Collections.emptyList(), servletRequest, servletResponse);
        Assert.assertEquals("[]", output);

        // one hit, no itemResourceType
        hits.add(hit);
        output = viewHandler.generateHtmlOutput(hits, servletRequest, servletResponse);
        Assert.assertNotNull(output);
        Assert.assertTrue(output.startsWith("[com.day.cq.wcm.core.contentfinder.Hit@"));

        // no hit, with itemResourceType
        Mockito.when(servletRequest.getParameter("itemResourceType")).thenReturn("item/resource/type");
        output = viewHandler.generateHtmlOutput(Collections.emptyList(), servletRequest, servletResponse);
        Assert.assertEquals("", output);

        // one hit, with itemResourceType, no resource
        output = viewHandler.generateHtmlOutput(hits, servletRequest, servletResponse);
        Assert.assertEquals("", output);

        // one hit with itemResourceType and resource
        hit.set("resource", Mockito.mock(Resource.class));
        output = viewHandler.generateHtmlOutput(hits, servletRequest, servletResponse);
        Assert.assertEquals("Hit" + LINE_SEPARATOR, output);

        // more hits with itemResourceType and resource
        hits.add(hit2);
        output = viewHandler.generateHtmlOutput(hits, servletRequest, servletResponse);
        Assert.assertEquals("Hit" + LINE_SEPARATOR + "Hit" + LINE_SEPARATOR, output);
    }
}