/**
 * Copyright (C) 2011  JTalks.org Team
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * This library 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
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jtalks.jcommune.model.dao.search.hibernate;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.jtalks.jcommune.model.entity.*;
import org.jtalks.jcommune.model.dto.PageRequest;
import org.jtalks.jcommune.model.search.SearchRequestFilter;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.List;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

/**
 * @author Anuar Nurmakanov
 */
@ContextConfiguration(locations = {"classpath:/org/jtalks/jcommune/model/entity/applicationContext-dao.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class TopicHibernateSearchDaoTest extends AbstractTransactionalTestNGSpringContextTests {
    private static final int PAGE_SIZE = 50;
    private static final String TOPIC_CONTENT = "topicContent";
    private static final PageRequest DEFAULT_PAGE_REQUEST = new PageRequest("1", 50);
    @Autowired
    private SessionFactory sessionFactory;
    @Autowired
    private TopicHibernateSearchDao topicSearchDao;
    @Mock
    private SearchRequestFilter invalidCharactersFilter;
    @Mock
    private SearchRequestFilter stopWordsFilter;

    private FullTextSession fullTextSession;

    @BeforeMethod
    public void init() {
        Session session = sessionFactory.getCurrentSession();
        PersistedObjectsFactory.setSession(session);

        MockitoAnnotations.initMocks(this);
        List<SearchRequestFilter> filters = Arrays.asList(invalidCharactersFilter, stopWordsFilter);
        topicSearchDao.setFilters(filters);
    }

    private void configureMocks(String searchText, String result) {
        Mockito.when(invalidCharactersFilter.filter(searchText)).thenReturn(result);
        Mockito.when(stopWordsFilter.filter(searchText)).thenReturn(result);
    }

    @BeforeMethod
    public void initHibernateSearch() throws InterruptedException {
        Session session = sessionFactory.getCurrentSession();
        fullTextSession = Search.getFullTextSession(session);
        fullTextSession.createIndexer().startAndWait();
    }

    @AfterMethod
    public void clearIndexes() {
        fullTextSession.purgeAll(Topic.class);
        fullTextSession.flushToIndexes();
    }

	/*===== Paging testing =====*/

    @Test
    public void testSearchPaging() {
        int totalSize = 50;
        int pageCount = 2;
        int pageSize = totalSize / pageCount;
        String searchText = "JCommune";
        PageRequest pageRequest = new PageRequest("1", pageSize);
        List<Topic> topicList = PersistedObjectsFactory.createAndSaveTopicList(totalSize);
        for (Topic topic : topicList) {
            topic.setTitle(searchText);
        }

        saveAndFlushIndexes(topicList);
        configureMocks(searchText, searchText);

        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                searchText, pageRequest, Arrays.asList(topicList.get(0).getBranch().getId()));

        assertEquals(searchResultPage.getContent().size(), pageSize, "Incorrect count of topics in one page.");
        assertEquals(searchResultPage.getTotalElements(), totalSize, "Incorrect total count.");
        assertEquals(searchResultPage.getTotalPages(), pageCount, "Incorrect count of pages.");

    }

	/*===== Testing of different variations of the search. =====*/

    @Test
    public void testSearchWithFullyDirtySearchText() {
        configureMocks(StringUtils.EMPTY, StringUtils.EMPTY);

        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                StringUtils.EMPTY, DEFAULT_PAGE_REQUEST, Arrays.asList(1L));

        Assert.assertTrue(!searchResultPage.hasContent(), "Search result must be empty.");
    }

    @Test(dataProvider = "parameterFullPhraseSearch")
    public void testFullPhraseSearch(String content) {
        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.setTitle(content);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));
        configureMocks(content, content);

        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                content, DEFAULT_PAGE_REQUEST, Arrays.asList(expectedTopic.getBranch().getId()));

        Assert.assertTrue(searchResultPage.hasContent(), "Search result must not be empty.");
        for (Topic topic : searchResultPage.getContent()) {
            Assert.assertEquals(expectedTopic.getTitle(), topic.getTitle(),
                    "Content from the index should be the same as in the database.");
        }
    }

    @Test
    public void testFullPhraseSearchPageNumberTooLow() {
        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.setTitle(TOPIC_CONTENT);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));
        configureMocks(TOPIC_CONTENT, TOPIC_CONTENT);

        PageRequest pageRequest = new PageRequest("-1", PAGE_SIZE);
        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                TOPIC_CONTENT, pageRequest, Arrays.asList(expectedTopic.getBranch().getId()));

        Assert.assertEquals(searchResultPage.getNumber(), 1);
        Assert.assertTrue(searchResultPage.hasContent(), "Search result must not be empty.");
        for (Topic topic : searchResultPage.getContent()) {
            Assert.assertEquals(expectedTopic.getTitle(), topic.getTitle(),
                    "Content from the index should be the same as in the database.");
        }
    }

    @Test
    public void testFullPhraseSearchPageNumberTooBig() {
        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.setTitle(TOPIC_CONTENT);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));
        configureMocks(TOPIC_CONTENT, TOPIC_CONTENT);

        PageRequest pageRequest = new PageRequest("1000", 50);
        Page<Topic> searchResultPage =
                topicSearchDao.searchByTitleAndContent(TOPIC_CONTENT, pageRequest,
                        Arrays.asList(expectedTopic.getBranch().getId()));

        Assert.assertEquals(searchResultPage.getNumber(), 1);
        Assert.assertTrue(searchResultPage.hasContent(), "Search result must not be empty.");
        for (Topic topic : searchResultPage.getContent()) {
            Assert.assertEquals(expectedTopic.getTitle(), topic.getTitle(),
                    "Content from the index should be the same as in the database.");
        }
    }

    @Test(dataProvider = "parameterFullPhraseSearch")
    public void testPostContentSearch(String content) {
        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.getLastPost().setPostContent(content);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));
        configureMocks(content, content);

        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                content, DEFAULT_PAGE_REQUEST, Arrays.asList(expectedTopic.getBranch().getId()));

        Assert.assertTrue(searchResultPage.hasContent(), "Search result must not be empty.");
        for (Topic topic : searchResultPage.getContent()) {
            Assert.assertEquals(expectedTopic.getTitle(), topic.getTitle(),
                    "Content from the index should be the same as in the database.");
        }
    }

    @DataProvider(name = "parameterFullPhraseSearch")
    public Object[][] parameterFullPhraseSearch() {
        return new Object[][]{
                {"Содержимое темы."},
                {"Topic content."}
        };
    }

    @Test(dataProvider = "parameterPiecePhraseSearch")
    public void testPiecePhraseSearch(String firstPiece, char delimeter, String secondPiece) {
        String content = firstPiece + delimeter + secondPiece;

        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.setTitle(content);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));

        for (String piece : Arrays.asList(firstPiece, secondPiece)) {
            configureMocks(piece, piece);

            Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                    piece, DEFAULT_PAGE_REQUEST, Arrays.asList(expectedTopic.getBranch().getId()));

            Assert.assertTrue(searchResultPage.hasContent(), "Search result must not be empty.");
        }
    }

    @DataProvider(name = "parameterPiecePhraseSearch")
    public Object[][] parameterPiecePhraseSearch() {
        return new Object[][]{
                {"Содержимое", ' ', "топика"},
                {"Topic", ' ', "content"}
        };
    }

    @Test(dataProvider = "parameterIncorrectPhraseSearch")
    public void testIncorrectPhraseSearch(String correct, String incorrect) {
        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.setTitle(correct);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));
        configureMocks(incorrect, incorrect);

        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                incorrect, DEFAULT_PAGE_REQUEST, Arrays.asList(expectedTopic.getBranch().getId()));

        Assert.assertTrue(!searchResultPage.hasContent(), "Search result must be empty.");
    }

    private <E> void saveAndFlushIndexes(List<E> entityList) {
        for (E entity : entityList) {
            fullTextSession.save(entity);
        }
        fullTextSession.flushToIndexes();
    }

    @DataProvider(name = "parameterIncorrectPhraseSearch")
    public Object[][] parameterIncorrectPhraseSearch() {
        return new Object[][]{
                {"Содержимое поста", "Железный человек"},
                {"Post content", "Iron Man"}
        };
    }

    @Test(dataProvider = "parameterSearchByRoot")
    public void testSearchByRoot(String word, String wordWithSameRoot) {
        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.setTitle(word);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));
        configureMocks(wordWithSameRoot, wordWithSameRoot);

        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                wordWithSameRoot, DEFAULT_PAGE_REQUEST, Arrays.asList(expectedTopic.getBranch().getId()));
        Assert.assertTrue(searchResultPage.hasContent(), "Search result must not be empty.");
    }

    @DataProvider(name = "parameterSearchByRoot")
    public Object[][] parameterSearchByRoot() {
        return new Object[][]{
                {"Keys", "Key"},
                {"Key", "Keys"},
                {"testing", "Tests"},
                {"tests", "TeStIng"},
                {"Полеты", "полет"},
                {"барабан", "барабаны"}
        };
    }

    @Test(dataProvider = "parameterSearchByBbCodes")
    public void testSearchByBbCodes(String content, String bbCode) {
        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.getLastPost().setPostContent(content);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));
        configureMocks(bbCode, bbCode);

        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                bbCode, DEFAULT_PAGE_REQUEST, Arrays.asList(expectedTopic.getBranch().getId()));
        Assert.assertTrue(!searchResultPage.hasContent(), "Search result must be empty.");
    }

    @DataProvider(name = "parameterSearchByBbCodes")
    public Object[][] parameterSearchByBbCodes() {
        return new Object[][]{
                {"[code=java]spring[/code]", "code"},
                {"[b]gwt[/b]", "b"}
        };
    }

    @Test(dataProvider = "parameterSearchByBbCodesContent")
    public void testSearchByBbCodesContent(String content, String bbCodeContent) {
        Topic expectedTopic = PersistedObjectsFactory.getDefaultTopic();
        expectedTopic.getLastPost().setPostContent(content);

        saveAndFlushIndexes(Arrays.asList(expectedTopic));
        configureMocks(bbCodeContent, bbCodeContent);

        Page<Topic> searchResultPage = topicSearchDao.searchByTitleAndContent(
                bbCodeContent, DEFAULT_PAGE_REQUEST, Arrays.asList(expectedTopic.getBranch().getId()));
        Assert.assertTrue(searchResultPage.hasContent(), "Search result must not be empty.");
    }

    @DataProvider(name = "parameterSearchByBbCodesContent")
    public Object[][] parameterSearchByBbCodesContent() {
        return new Object[][]{
                {"[code=java]code[/code]", "code"},
                {"[b]b[/b]", "b"}
        };
    }
}
