# Copyright (c) 2019, JINGROW and Contributors
# License: MIT. See LICENSE
import requests

import jingrow
from jingrow.core.pagetype.scheduled_job_type.scheduled_job_type import sync_jobs
from jingrow.jingrowclient import JingrowClient, JingrowException
from jingrow.tests.utils import JingrowTestCase
from jingrow.utils import get_site_url

scripts = [
	dict(
		name="test_todo",
		script_type="PageType Event",
		pagetype_event="Before Insert",
		reference_pagetype="ToDo",
		script="""
if "test" in pg.description:
	pg.status = 'Closed'
""",
	),
	dict(
		name="test_todo_validate",
		script_type="PageType Event",
		pagetype_event="Before Insert",
		reference_pagetype="ToDo",
		script="""
if "validate" in pg.description:
	raise jingrow.ValidationError
""",
	),
	dict(
		name="test_api",
		script_type="API",
		api_method="test_server_script",
		allow_guest=1,
		script="""
jingrow.response['message'] = 'hello'
""",
	),
	dict(
		name="test_return_value",
		script_type="API",
		api_method="test_return_value",
		allow_guest=1,
		script="""
jingrow.flags = 'hello'
""",
	),
	dict(
		name="test_permission_query",
		script_type="Permission Query",
		reference_pagetype="ToDo",
		script="""
conditions = '1 = 1'
""",
	),
	dict(
		name="test_invalid_namespace_method",
		script_type="PageType Event",
		pagetype_event="Before Insert",
		reference_pagetype="Note",
		script="""
jingrow.method_that_doesnt_exist("do some magic")
""",
	),
	dict(
		name="test_todo_commit",
		script_type="PageType Event",
		pagetype_event="Before Save",
		reference_pagetype="ToDo",
		disabled=1,
		script="""
jingrow.db.commit()
""",
	),
	dict(
		name="test_add_index",
		script_type="PageType Event",
		pagetype_event="Before Save",
		reference_pagetype="ToDo",
		disabled=1,
		script="""
jingrow.db.add_index("Todo", ["color", "date"])
""",
	),
	dict(
		name="test_before_rename",
		script_type="PageType Event",
		pagetype_event="After Rename",
		reference_pagetype="Role",
		script="""
pg.desk_access =0
pg.save()
""",
	),
	dict(
		name="test_after_rename",
		script_type="PageType Event",
		pagetype_event="After Rename",
		reference_pagetype="Role",
		script="""
pg.disabled =1
pg.save()
""",
	),
]


class TestServerScript(JingrowTestCase):
	@classmethod
	def setUpClass(cls):
		super().setUpClass()
		jingrow.db.truncate("Server Script")
		jingrow.get_pg("User", "Administrator").add_roles("Script Manager")
		for script in scripts:
			script_pg = jingrow.get_pg(pagetype="Server Script")
			script_pg.update(script)
			script_pg.insert()
		cls.enable_safe_exec()
		jingrow.db.commit()
		return super().setUpClass()

	@classmethod
	def tearDownClass(cls):
		jingrow.db.commit()
		jingrow.db.truncate("Server Script")
		jingrow.cache.delete_value("server_script_map")

	def setUp(self):
		jingrow.cache.delete_value("server_script_map")

	def test_pagetype_event(self):
		todo = jingrow.get_pg(dict(pagetype="ToDo", description="hello")).insert()
		self.assertEqual(todo.status, "Open")

		todo = jingrow.get_pg(dict(pagetype="ToDo", description="test todo")).insert()
		self.assertEqual(todo.status, "Closed")

		self.assertRaises(
			jingrow.ValidationError, jingrow.get_pg(dict(pagetype="ToDo", description="validate me")).insert
		)

		role = jingrow.get_pg(pagetype="Role", role_name="_Test Role 9").insert(ignore_if_duplicate=True)
		role.rename("_Test Role 10")
		role.reload()
		self.assertEqual(role.disabled, 1)
		self.assertEqual(role.desk_access, 0)

	def test_api(self):
		response = requests.post(get_site_url(jingrow.local.site) + "/api/action/test_server_script")
		self.assertEqual(response.status_code, 200)
		self.assertEqual("hello", response.json()["message"])

	def test_api_return(self):
		self.assertEqual(jingrow.get_pg("Server Script", "test_return_value").execute_method(), "hello")

	def test_permission_query(self):
		if jingrow.conf.db_type == "mariadb":
			self.assertTrue("where (1 = 1)" in jingrow.db.get_list("ToDo", run=False))
		else:
			self.assertTrue("where (1 = '1')" in jingrow.db.get_list("ToDo", run=False))
		self.assertTrue(isinstance(jingrow.db.get_list("ToDo"), list))

	def test_attribute_error(self):
		"""Raise AttributeError if method not found in Namespace"""
		note = jingrow.get_pg({"pagetype": "Note", "title": "Test Note: Server Script"})
		self.assertRaises(AttributeError, note.insert)

	def test_syntax_validation(self):
		server_script = scripts[0]
		server_script["script"] = "js || code.?"

		with self.assertRaises(jingrow.ValidationError) as se:
			jingrow.get_pg(pagetype="Server Script", **server_script).insert()

		self.assertTrue(
			"invalid python code" in str(se.exception).lower(), msg="Python code validation not working"
		)

	def test_commit_in_pagetype_event(self):
		server_script = jingrow.get_pg("Server Script", "test_todo_commit")
		server_script.disabled = 0
		server_script.save()

		self.assertRaises(AttributeError, jingrow.get_pg(dict(pagetype="ToDo", description="test me")).insert)

		server_script.disabled = 1
		server_script.save()

	def test_add_index_in_pagetype_event(self):
		server_script = jingrow.get_pg("Server Script", "test_add_index")
		server_script.disabled = 0
		server_script.save()

		self.assertRaises(AttributeError, jingrow.get_pg(dict(pagetype="ToDo", description="test me")).insert)

		server_script.disabled = 1
		server_script.save()

	def test_restricted_qb(self):
		todo = jingrow.get_pg(pagetype="ToDo", description="QbScriptTestNote")
		todo.insert()

		script = jingrow.get_pg(
			pagetype="Server Script",
			name="test_qb_restrictions",
			script_type="API",
			api_method="test_qb_restrictions",
			allow_guest=1,
			# whitelisted update
			script=f"""
jingrow.db.set_value("ToDo", "{todo.name}", "description", "safe")
""",
		)
		script.insert()
		script.execute_method()

		todo.reload()
		self.assertEqual(todo.description, "safe")

		# unsafe update
		script.script = f"""
todo = jingrow.qb.PageType("ToDo")
jingrow.qb.update(todo).set(todo.description, "unsafe").where(todo.name == "{todo.name}").run()
"""
		script.save()
		self.assertRaises(jingrow.PermissionError, script.execute_method)
		todo.reload()
		self.assertEqual(todo.description, "safe")

		# safe select
		script.script = f"""
todo = jingrow.qb.PageType("ToDo")
jingrow.qb.from_(todo).select(todo.name).where(todo.name == "{todo.name}").run()
"""
		script.save()
		script.execute_method()

	def test_scripts_all_the_way_down(self):
		# why not
		script = jingrow.get_pg(
			pagetype="Server Script",
			name="test_nested_scripts_1",
			script_type="API",
			api_method="test_nested_scripts_1",
			script="""log("nothing")""",
		)
		script.insert()
		script.execute_method()

		script = jingrow.get_pg(
			pagetype="Server Script",
			name="test_nested_scripts_2",
			script_type="API",
			api_method="test_nested_scripts_2",
			script="""jingrow.call("test_nested_scripts_1")""",
		)
		script.insert()
		script.execute_method()

	def test_server_script_rate_limiting(self):
		script1 = jingrow.get_pg(
			pagetype="Server Script",
			name="rate_limited_server_script",
			script_type="API",
			enable_rate_limit=1,
			allow_guest=1,
			rate_limit_count=5,
			api_method="rate_limited_endpoint",
			script="""jingrow.flags = {"test": True}""",
		)

		script1.insert()

		script2 = jingrow.get_pg(
			pagetype="Server Script",
			name="rate_limited_server_script2",
			script_type="API",
			enable_rate_limit=1,
			allow_guest=1,
			rate_limit_count=5,
			api_method="rate_limited_endpoint2",
			script="""jingrow.flags = {"test": False}""",
		)

		script2.insert()

		jingrow.db.commit()

		site = jingrow.utils.get_site_url(jingrow.local.site)
		client = JingrowClient(site)

		# Exhaust rate limit
		for _ in range(5):
			client.get_api(script1.api_method)

		self.assertRaises(JingrowException, client.get_api, script1.api_method)

		# Exhaust rate limit
		for _ in range(5):
			client.get_api(script2.api_method)

		self.assertRaises(JingrowException, client.get_api, script2.api_method)

		script1.delete()
		script2.delete()
		jingrow.db.commit()

	def test_server_script_scheduled(self):
		scheduled_script = jingrow.get_pg(
			pagetype="Server Script",
			name="scheduled_script_wo_cron",
			script_type="Scheduler Event",
			script="""jingrow.flags = {"test": True}""",
			event_frequency="Hourly",
		).insert()

		cron_script = jingrow.get_pg(
			pagetype="Server Script",
			name="scheduled_script_w_cron",
			script_type="Scheduler Event",
			script="""jingrow.flags = {"test": True}""",
			event_frequency="Cron",
			cron_format="0 0 1 1 *",  # 1st january
		).insert()

		# Ensure that jobs remain in DB after migrate
		sync_jobs()
		self.assertTrue(jingrow.db.exists("Scheduled Job Type", {"server_script": scheduled_script.name}))

		cron_job_name = jingrow.db.get_value("Scheduled Job Type", {"server_script": cron_script.name})
		self.assertTrue(cron_job_name)

		cron_job = jingrow.get_pg("Scheduled Job Type", cron_job_name)
		self.assertEqual(cron_job.next_execution.day, 1)
		self.assertEqual(cron_job.next_execution.month, 1)

		cron_script.cron_format = "0 0 2 1 *"  # 2nd january
		cron_script.save()
		cron_job.reload()
		self.assertEqual(cron_job.next_execution.day, 2)
