# Copyright (c) 2021, JINGROW and Contributors
# License: MIT. See LICENSE

import re
from functools import partial

import jingrow
from jingrow.app import make_form_dict
from jingrow.desk.search import get_names_for_mentions, search_link, search_widget
from jingrow.tests.utils import JingrowTestCase
from jingrow.utils import set_request
from jingrow.website.serve import get_response


class TestSearch(JingrowTestCase):
	def setUp(self):
		if self._testMethodName == "test_link_field_order":
			setup_test_link_field_order(self)
			self.addCleanup(teardown_test_link_field_order, self)

	def test_search_field_sanitizer(self):
		results = search_link("PageType", "User", query=None, filters=None, page_length=20, searchfield="name")
		self.assertTrue("User" in results[0]["value"])

		# raise exception on injection
		for searchfield in (
			"1=1",
			"select * from tabSessions) --",
			"name or (select * from tabSessions)",
			"*",
			";",
			"select`sid`from`tabSessions`",
		):
			self.assertRaises(
				jingrow.DataError,
				search_link,
				"PageType",
				"User",
				query=None,
				filters=None,
				page_length=20,
				searchfield=searchfield,
			)

	def test_only_enabled_in_mention(self):
		email = "test_disabled_user_in_mentions@example.com"
		jingrow.delete_pg("User", email)
		if not jingrow.db.exists("User", email):
			user = jingrow.new_pg("User")
			user.update(
				{
					"email": email,
					"first_name": email.split("@", 1)[0],
					"enabled": False,
					"allowed_in_mentions": True,
				}
			)
			# saved when roles are added
			user.add_roles(
				"System Manager",
			)

		names_for_mention = [user.get("id") for user in get_names_for_mentions("")]
		self.assertNotIn(email, names_for_mention)

	def test_link_field_order(self):
		# Making a request to the search_link with the tree pagetype
		results = search_link(
			pagetype=self.tree_pagetype_name,
			txt="all",
			query=None,
			filters=None,
			page_length=20,
			searchfield=None,
		)

		# Check whether the result is sorted or not
		self.assertEqual(self.parent_pagetype_name, results[0]["value"])

		# Check whether searching for parent also list out children
		self.assertEqual(len(results), len(self.child_pagetypes_names) + 1)

	# Search for the word "pay", part of the word "pays" (country) in french.
	def test_link_search_in_foreign_language(self):
		try:
			jingrow.local.lang = "fr"
			output = search_widget(pagetype="PageType", txt="pay", page_length=20)

			result = [["found" for x in y if x == "Country"] for y in output]
			self.assertTrue(["found"] in result)
		finally:
			jingrow.local.lang = "en"

	def test_pagetype_search_in_foreign_language(self):
		def do_search(txt: str):
			return search_link(
				pagetype="PageType",
				txt=txt,
				query="jingrow.core.report.permitted_documents_for_user.permitted_documents_for_user.query_pagetypes",
				filters={"user": "Administrator"},
				page_length=20,
				searchfield=None,
			)

		try:
			jingrow.local.lang = "en"
			results = do_search("user")
			self.assertIn("User", [x["value"] for x in results])

			jingrow.local.lang = "fr"
			results = do_search("utilisateur")
			self.assertIn("User", [x["value"] for x in results])

			jingrow.local.lang = "de"
			results = do_search("nutzer")
			self.assertIn("User", [x["value"] for x in results])
		finally:
			jingrow.local.lang = "en"

	def test_validate_and_sanitize_search_inputs(self):
		# should raise error if searchfield is injectable
		self.assertRaises(
			jingrow.DataError,
			get_data,
			*("User", "Random", "select * from tabSessions) --", "1", "10", dict()),
		)

		# page_len and start should be converted to int
		self.assertListEqual(
			get_data("User", "Random", "email", "name or (select * from tabSessions)", "10", dict()),
			["User", "Random", "email", 0, 10, {}],
		)
		self.assertListEqual(
			get_data("User", "Random", "email", page_len="2", start="10", filters=dict()),
			["User", "Random", "email", 10, 2, {}],
		)

		# PageType can be passed as None which should be accepted
		self.assertListEqual(
			get_data(None, "Random", "email", "2", "10", dict()), [None, "Random", "email", 2, 10, {}]
		)

		# return empty string if passed pagetype is invalid
		self.assertListEqual(get_data("Random PageType", "Random", "email", "2", "10", dict()), [])

		# should not fail if function is called via jingrow.call with extra arguments
		args = ("Random PageType", "Random", "email", "2", "10", dict())
		kwargs = {"as_dict": False}
		self.assertListEqual(jingrow.call("jingrow.tests.test_search.get_data", *args, **kwargs), [])

		# should not fail if query has @ symbol in it
		results = search_link("User", "user@random", searchfield="name")
		self.assertListEqual(results, [])

	def test_reference_pagetype(self):
		"""search query methods should get reference_pagetype if they want"""
		results = search_link(
			pagetype="User",
			txt="",
			filters=None,
			page_length=20,
			reference_pagetype="ToDo",
			query="jingrow.tests.test_search.query_with_reference_pagetype",
		)
		self.assertListEqual(results, [])

	def test_search_relevance(self):
		search = partial(search_link, pagetype="Language", filters=None, page_length=10)
		for row in search(txt="e"):
			self.assertTrue(row["value"].startswith("e"))

		for row in search(txt="es"):
			self.assertIn("es", row["value"])

		# Assume that "es" is used at least 10 times, it should now be first
		jingrow.db.set_value("Language", "es", "idx", 10)
		self.assertEqual("es", search(txt="es")[0]["value"])

	def test_search_with_paren(self):
		search = partial(search_link, pagetype="Language", filters=None, page_length=10)
		result = search(txt="(txt)")
		self.assertEqual(result, [])


@jingrow.validate_and_sanitize_search_inputs
def get_data(pagetype, txt, searchfield, start, page_len, filters):
	return [pagetype, txt, searchfield, start, page_len, filters]


@jingrow.whitelist()
@jingrow.validate_and_sanitize_search_inputs
def query_with_reference_pagetype(pagetype, txt, searchfield, start, page_len, filters, reference_pagetype=None):
	return []


def setup_test_link_field_order(TestCase):
	TestCase.tree_pagetype_name = "Test Tree Order"
	TestCase.child_pagetype_list = []
	TestCase.child_pagetypes_names = ["USA", "India", "Russia", "China"]
	TestCase.parent_pagetype_name = "All Territories"

	# Create Tree pagetype
	if not jingrow.db.exists("PageType", TestCase.tree_pagetype_name):
		TestCase.tree_pg = jingrow.get_pg(
			{
				"pagetype": "PageType",
				"name": TestCase.tree_pagetype_name,
				"module": "Custom",
				"custom": 1,
				"is_tree": 1,
				"autoname": "field:random",
				"fields": [{"fieldname": "random", "label": "Random", "fieldtype": "Data"}],
			}
		).insert()
		TestCase.tree_pg.search_fields = "parent_test_tree_order"
		TestCase.tree_pg.save()
	else:
		TestCase.tree_pg = jingrow.get_pg("PageType", TestCase.tree_pagetype_name)

	# Create root for the tree pagetype
	if not jingrow.db.exists(TestCase.tree_pagetype_name, {"random": TestCase.parent_pagetype_name}):
		jingrow.get_pg(
			{"pagetype": TestCase.tree_pagetype_name, "random": TestCase.parent_pagetype_name, "is_group": 1}
		).insert(ignore_if_duplicate=True)

	# Create children for the root
	for child_name in TestCase.child_pagetypes_names:
		temp = jingrow.get_pg(
			{
				"pagetype": TestCase.tree_pagetype_name,
				"random": child_name,
				"parent_test_tree_order": TestCase.parent_pagetype_name,
			}
		).insert(ignore_if_duplicate=True)
		TestCase.child_pagetype_list.append(temp)


def teardown_test_link_field_order(TestCase):
	# Deleting all the created pagetype
	for child_pagetype in TestCase.child_pagetype_list:
		child_pagetype.delete()

	jingrow.delete_pg(
		TestCase.tree_pagetype_name,
		TestCase.parent_pagetype_name,
		ignore_permissions=True,
		force=True,
		for_reload=True,
	)

	TestCase.tree_pg.delete()


class TestWebsiteSearch(JingrowTestCase):
	def get(self, path, user="Guest"):
		jingrow.set_user(user)
		set_request(method="GET", path=path)
		make_form_dict(jingrow.local.request)
		response = get_response()
		jingrow.set_user("Administrator")
		return response

	def test_basic_search(self):
		no_search = self.get("/search")
		self.assertEqual(no_search.status_code, 200)

		response = self.get("/search?q=b")
		self.assertEqual(response.status_code, 200)
		self.assertIn("Search Results", response.get_data(as_text=True))
