from datetime import datetime
from ploneintranet.api.tests.mocks import FakeUserprofile
from ploneintranet.search.browser.searchresults import SearchResultsView
from ploneintranet.search.interfaces import ISiteSearch
from ploneintranet.search.solr import testing
from scorched.search import LuceneQuery
from unittest import mock
from zope.interface import implementer


@implementer(ISiteSearch)
class FakeSiteSearch:
    def __init__(self):
        self.queries = []
        self.executed = []

    def query(self, query=None, phrase=None, **kw):
        if phrase is not None:
            kw["phrase"] = phrase
            self.queries.append(kw)
        elif query is not None:
            self.execute(query, **kw)

    def execute(self, query, secure=True, **kw):
        self.executed.append(dict(query=query, secure=secure, kw=kw))

    def Q(self, *args, **kwargs):
        q = LuceneQuery()
        q.add(args, kwargs)
        return q


class TestAdvancedSearch(testing.IntegrationTestCase):
    def setUp(self):
        super().setUp()
        self.search_util = FakeSiteSearch()
        self.layer["portal"].getSiteManager().registerUtility(
            component=self.search_util, provided=ISiteSearch
        )

    def get_search_view(self, request=None):
        if request is None:
            request = self.layer["request"]
        return SearchResultsView(self.layer["app"]["plone"], request)

    def test_simple_advanced_search(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "test",
            }
        ]
        view = self.get_search_view(request=request)

        view.search_response()

        self.assertGreaterEqual(
            len(self.search_util.executed), 1, msg="execute() was not called"
        )
        query = self.search_util.executed[0]["query"]
        self.assertTrue(
            isinstance(query, LuceneQuery), msg="Query is not the expected type"
        )
        self.assertEqual(query.terms["title"], {"test"})

    def test_and(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "test",
            },
            {
                "and-or": "and",
                "data-field": "SearchableText",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "flower",
            },
        ]
        view = self.get_search_view(request=request)

        view.search_response()

        query = self.search_util.executed[0]["query"]
        self.assertEqual(list(query.terms.keys()), [])
        self.assertTrue(query._and)
        self.assertEqual(query.subqueries[0].terms["title"], {"test"})
        self.assertEqual(query.subqueries[1].terms["SearchableText"], {"flower"})

    def test_or(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "test",
            },
            {
                "and-or": "or",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "flower",
            },
        ]
        view = self.get_search_view(request=request)

        view.search_response()

        query = self.search_util.executed[0]["query"]
        self.assertEqual(list(query.terms.keys()), [])
        self.assertTrue(query._or)
        self.assertEqual(query.subqueries[0].terms["title"], {"test"})
        self.assertEqual(query.subqueries[1].terms["title"], {"flower"})

    def test_and_or(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "SearchableText",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "test",
            },
            {
                "and-or": "or",
                "data-field": "SearchableText",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "flower",
            },
            {
                "and-or": "and",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "leaf",
            },
        ]
        view = self.get_search_view(request=request)

        view.search_response()

        query = self.search_util.executed[0]["query"]
        # Assuming that subqueries (search rules) have implicit parentheses in
        # the order that they are coming in, i.e.
        # A or B and C
        # is interpreted as
        # (A | B) & C
        self.assertTrue(query._and)
        self.assertTrue(query.subqueries[0]._or)
        self.assertEqual(
            query.subqueries[0].subqueries[0].terms["SearchableText"], {"test"}
        )
        self.assertEqual(
            query.subqueries[0].subqueries[1].terms["SearchableText"], {"flower"}
        )
        self.assertEqual(query.subqueries[1].terms["title"], {"leaf"})

    def test_not(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains-not",
                "lemma-value": "test",
            }
        ]
        view = self.get_search_view(request=request)

        view.search_response()

        query = self.search_util.executed[0]["query"]
        self.assertTrue(query._not)
        self.assertEqual(query.subqueries[0].terms["title"], {"test"})

    def test_reject_operator_on_first_rule(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "and",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains-not",
                "lemma-value": "test",
            }
        ]
        view = self.get_search_view(request=request)

        self.assertRaises(ValueError, view.search_response)

    def test_date_equals(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "modified",
                "date-operator": "is",
                "date-value": "2018-01-26",
                "lemma-operator": "contains",
                "lemma-value": "",
            }
        ]
        view = self.get_search_view(request=request)

        view.search_response()

        query = self.search_util.executed[0]["query"]
        self.assertIn(
            (
                "modified",
                "range",
                ((datetime(2018, 1, 26, 0, 0), datetime(2018, 1, 26, 23, 59, 59)),),
            ),
            query.ranges,
        )

    def test_date_before(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "modified",
                "date-operator": "is-before",
                "date-value": "2018-01-26",
                "lemma-operator": "contains",
                "lemma-value": "",
            }
        ]
        view = self.get_search_view(request=request)

        view.search_response()

        query = self.search_util.executed[0]["query"]
        self.assertIn(("modified", "lt", (datetime(2018, 1, 26, 0, 0),)), query.ranges)

    def test_date_after(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "modified",
                "date-operator": "is-after",
                "date-value": "2018-01-26",
                "lemma-operator": "contains",
                "lemma-value": "",
            }
        ]
        view = self.get_search_view(request=request)

        view.search_response()

        query = self.search_util.executed[0]["query"]
        self.assertIn(("modified", "gt", (datetime(2018, 1, 26, 0, 0),)), query.ranges)

    def test_start_date(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "test",
            }
        ]
        request.form["start_date"] = "2018-02-14"
        view = self.get_search_view(request=request)

        view.search_response()

        self.assertGreaterEqual(
            len(self.search_util.executed), 1, msg="execute() was not called"
        )
        kw = self.search_util.executed[0]["kw"]
        self.assertIn("start_date", kw)

    def test_filters(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "test",
            }
        ]
        request.form["friendly_type_name"] = "PDF"
        request.form["include_archived"] = 1
        view = self.get_search_view(request=request)

        view.search_response()

        self.assertGreaterEqual(
            len(self.search_util.executed), 1, msg="execute() was not called"
        )
        kw = self.search_util.executed[0]["kw"]
        self.assertIn("filters", kw)
        self.assertIn("portal_type", kw["filters"])
        self.assertNotIn("!is_archived", kw["filters"])

    def test_sorting(self):
        with mock.patch(
            "ploneintranet.api.userprofile.get_current", return_value=FakeUserprofile()
        ):
            request = self.layer["request"].clone()
            request.form["search-mode"] = "advanced"
            request.form["search-rule"] = [
                {
                    "and-or": "",
                    "data-field": "title",
                    "date-operator": "is",
                    "date-value": "",
                    "lemma-operator": "contains",
                    "lemma-value": "test",
                }
            ]
            request.form["results-sorting"] = "date"
            view = self.get_search_view(request=request)

            view.search_response()

            self.assertGreaterEqual(
                len(self.search_util.executed), 1, msg="execute() was not called"
            )
            kw = self.search_util.executed[0]["kw"]
            self.assertEqual(kw["sort"], "-created")

    def test_batching(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "test",
            }
        ]
        request.form["page"] = 3
        view = self.get_search_view(request=request)

        view.search_response()

        self.assertGreaterEqual(
            len(self.search_util.executed), 1, msg="execute() was not called"
        )
        kw = self.search_util.executed[0]["kw"]
        self.assertEqual(kw["start"], 20)
        self.assertEqual(kw["step"], 10)

    def test_unicode_query(self):
        request = self.layer["request"].clone()
        request.form["search-mode"] = "advanced"
        request.form["search-rule"] = [
            {
                "and-or": "",
                "data-field": "title",
                "date-operator": "is",
                "date-value": "",
                "lemma-operator": "contains",
                "lemma-value": "Störung",
            }
        ]
        view = self.get_search_view(request=request)
        view._get_advanced_query(FakeSiteSearch().Q)
