import itertools

import jingrow
from jingrow.core.pagetype.pagetype.test_pagetype import new_pagetype
from jingrow.query_builder import Field
from jingrow.query_builder.functions import Abs, Count, Ifnull, Max, Now, Timestamp
from jingrow.tests.test_query_builder import db_type_is, run_only_if
from jingrow.tests.utils import JingrowTestCase
from jingrow.utils.nestedset import get_ancestors_of, get_descendants_of


def create_tree_pgs():
	records = [
		{
			"some_fieldname": "Root Node",
			"parent_test_tree_pagetype": None,
			"is_group": 1,
		},
		{
			"some_fieldname": "Parent 1",
			"parent_test_tree_pagetype": "Root Node",
			"is_group": 1,
		},
		{
			"some_fieldname": "Parent 2",
			"parent_test_tree_pagetype": "Root Node",
			"is_group": 1,
		},
		{
			"some_fieldname": "Child 1",
			"parent_test_tree_pagetype": "Parent 1",
			"is_group": 0,
		},
		{
			"some_fieldname": "Child 2",
			"parent_test_tree_pagetype": "Parent 1",
			"is_group": 0,
		},
		{
			"some_fieldname": "Child 3",
			"parent_test_tree_pagetype": "Parent 2",
			"is_group": 0,
		},
	]

	tree_pagetype = new_pagetype("Test Tree PageType", is_tree=True, autoname="field:some_fieldname")
	tree_pagetype.insert()

	for record in records:
		d = jingrow.new_pg("Test Tree PageType")
		d.update(record)
		d.insert()


class TestQuery(JingrowTestCase):
	@run_only_if(db_type_is.MARIADB)
	def test_multiple_tables_in_filters(self):
		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				["*"],
				[
					["PageField", "name", "like", "f%"],
					["PageType", "parent", "=", "something"],
				],
			).get_sql(),
			"SELECT `tabPageType`.* FROM `tabPageType` LEFT JOIN `tabPageField` ON `tabPageField`.`parent`=`tabPageType`.`name` AND `tabPageField`.`parenttype`='PageType' WHERE `tabPageField`.`name` LIKE 'f%' AND `tabPageType`.`parent`='something'",
		)

	@run_only_if(db_type_is.MARIADB)
	def test_string_fields(self):
		self.assertEqual(
			jingrow.qb.get_query("User", fields="name, email", filters={"name": "Administrator"}).get_sql(),
			jingrow.qb.from_("User")
			.select(Field("name"), Field("email"))
			.where(Field("name") == "Administrator")
			.get_sql(),
		)
		self.assertEqual(
			jingrow.qb.get_query(
				"User", fields=["`name`, `email`"], filters={"name": "Administrator"}
			).get_sql(),
			jingrow.qb.from_("User")
			.select(Field("name"), Field("email"))
			.where(Field("name") == "Administrator")
			.get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"User", fields=["`tabUser`.`name`", "`tabUser`.`email`"], filters={"name": "Administrator"}
			).run(),
			jingrow.qb.from_("User")
			.select(Field("name"), Field("email"))
			.where(Field("name") == "Administrator")
			.run(),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"User",
				fields=["`tabUser`.`name` as owner", "`tabUser`.`email`"],
				filters={"name": "Administrator"},
			).run(as_dict=1),
			jingrow.qb.from_("User")
			.select(Field("name").as_("owner"), Field("email"))
			.where(Field("name") == "Administrator")
			.run(as_dict=1),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"User", fields=["`tabUser`.`name`, Count(`name`) as count"], filters={"name": "Administrator"}
			).run(),
			jingrow.qb.from_("User")
			.select(Field("name"), Count("name").as_("count"))
			.where(Field("name") == "Administrator")
			.run(),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"User",
				fields=["`tabUser`.`name`, Count(`name`) as `count`"],
				filters={"name": "Administrator"},
			).run(),
			jingrow.qb.from_("User")
			.select(Field("name"), Count("name").as_("count"))
			.where(Field("name") == "Administrator")
			.run(),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"User", fields="`tabUser`.`name`, Count(`name`) as `count`", filters={"name": "Administrator"}
			).run(),
			jingrow.qb.from_("User")
			.select(Field("name"), Count("name").as_("count"))
			.where(Field("name") == "Administrator")
			.run(),
		)

	def test_functions_fields(self):
		self.assertEqual(
			jingrow.qb.get_query("User", fields="Count(name)", filters={}).get_sql(),
			jingrow.qb.from_("User").select(Count(Field("name"))).get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query("User", fields=["Count(name)", "Max(name)"], filters={}).get_sql(),
			jingrow.qb.from_("User").select(Count(Field("name")), Max(Field("name"))).get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query("User", fields=["abs(name-email)", "Count(name)"], filters={}).get_sql(),
			jingrow.qb.from_("User")
			.select(Abs(Field("name") - Field("email")), Count(Field("name")))
			.get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query("User", fields=[Count("*")], filters={}).get_sql(),
			jingrow.qb.from_("User").select(Count("*")).get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query("User", fields="timestamp(creation, modified)", filters={}).get_sql(),
			jingrow.qb.from_("User").select(Timestamp(Field("creation"), Field("modified"))).get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"User", fields="Count(name) as count, Max(email) as max_email", filters={}
			).get_sql(),
			jingrow.qb.from_("User")
			.select(Count(Field("name")).as_("count"), Max(Field("email")).as_("max_email"))
			.get_sql(),
		)

	def test_qb_fields(self):
		user_pagetype = jingrow.qb.PageType("User")
		self.assertEqual(
			jingrow.qb.get_query(
				user_pagetype, fields=[user_pagetype.name, user_pagetype.email], filters={}
			).get_sql(),
			jingrow.qb.from_(user_pagetype).select(user_pagetype.name, user_pagetype.email).get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query(user_pagetype, fields=user_pagetype.email, filters={}).get_sql(),
			jingrow.qb.from_(user_pagetype).select(user_pagetype.email).get_sql(),
		)

	def test_aliasing(self):
		user_pagetype = jingrow.qb.PageType("User")
		self.assertEqual(
			jingrow.qb.get_query("User", fields=["name as owner", "email as id"], filters={}).get_sql(),
			jingrow.qb.from_(user_pagetype)
			.select(user_pagetype.name.as_("owner"), user_pagetype.email.as_("id"))
			.get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query(user_pagetype, fields="name as owner, email as id", filters={}).get_sql(),
			jingrow.qb.from_(user_pagetype)
			.select(user_pagetype.name.as_("owner"), user_pagetype.email.as_("id"))
			.get_sql(),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				user_pagetype, fields=["Count(name) as count", "email as id"], filters={}
			).get_sql(),
			jingrow.qb.from_(user_pagetype)
			.select(Count(Field("name")).as_("count"), user_pagetype.email.as_("id"))
			.get_sql(),
		)

	@run_only_if(db_type_is.MARIADB)
	def test_filters(self):
		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				fields=["name"],
				filters={"module.app_name": "jingrow"},
			).get_sql(),
			"SELECT `tabPageType`.`name` FROM `tabPageType` LEFT JOIN `tabModule Def` ON `tabModule Def`.`name`=`tabPageType`.`module` WHERE `tabModule Def`.`app_name`='jingrow'".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				fields=["name"],
				filters={"module.app_name": ("like", "frap%")},
			).get_sql(),
			"SELECT `tabPageType`.`name` FROM `tabPageType` LEFT JOIN `tabModule Def` ON `tabModule Def`.`name`=`tabPageType`.`module` WHERE `tabModule Def`.`app_name` LIKE 'frap%'".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				fields=["name"],
				filters={"permissions.role": "System Manager"},
			).get_sql(),
			"SELECT `tabPageType`.`name` FROM `tabPageType` LEFT JOIN `tabPagePerm` ON `tabPagePerm`.`parent`=`tabPageType`.`name` AND `tabPagePerm`.`parenttype`='PageType' WHERE `tabPagePerm`.`role`='System Manager'".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertRaisesRegex(
			jingrow.ValidationError,
			"Invalid filter",
			lambda: jingrow.qb.get_query(
				"PageType",
				fields=["name"],
				filters={"permissions.role": "System Manager"},
				validate_filters=True,
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				fields=["module"],
				filters="",
			).get_sql(),
			"SELECT `module` FROM `tabPageType` WHERE `name`=''".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				filters=["ToDo", "Note"],
			).get_sql(),
			"SELECT `name` FROM `tabPageType` WHERE `name` IN ('ToDo','Note')".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				filters={"name": ("in", [])},
			).get_sql(),
			"SELECT `name` FROM `tabPageType` WHERE `name` IN ('')".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				filters=[1, 2, 3],
			).get_sql(),
			"SELECT `name` FROM `tabPageType` WHERE `name` IN (1,2,3)".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				filters=[],
			).get_sql(),
			"SELECT `name` FROM `tabPageType`".replace("`", '"' if jingrow.db.db_type == "postgres" else "`"),
		)

	def test_implicit_join_query(self):
		self.maxDiff = None

		self.assertEqual(
			jingrow.qb.get_query(
				"Note",
				filters={"name": "Test Note Title"},
				fields=["name", "`tabNote Seen By`.`user` as seen_by"],
			).get_sql(),
			"SELECT `tabNote`.`name`,`tabNote Seen By`.`user` `seen_by` FROM `tabNote` LEFT JOIN `tabNote Seen By` ON `tabNote Seen By`.`parent`=`tabNote`.`name` AND `tabNote Seen By`.`parenttype`='Note' WHERE `tabNote`.`name`='Test Note Title'".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"Note",
				filters={"name": "Test Note Title"},
				fields=["name", "`tabNote Seen By`.`user` as seen_by", "`tabNote Seen By`.`idx` as idx"],
			).get_sql(),
			"SELECT `tabNote`.`name`,`tabNote Seen By`.`user` `seen_by`,`tabNote Seen By`.`idx` `idx` FROM `tabNote` LEFT JOIN `tabNote Seen By` ON `tabNote Seen By`.`parent`=`tabNote`.`name` AND `tabNote Seen By`.`parenttype`='Note' WHERE `tabNote`.`name`='Test Note Title'".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"Note",
				filters={"name": "Test Note Title"},
				fields=["name", "seen_by.user as seen_by", "`tabNote Seen By`.`idx` as idx"],
			).get_sql(),
			"SELECT `tabNote`.`name`,`tabNote Seen By`.`user` `seen_by`,`tabNote Seen By`.`idx` `idx` FROM `tabNote` LEFT JOIN `tabNote Seen By` ON `tabNote Seen By`.`parent`=`tabNote`.`name` AND `tabNote Seen By`.`parenttype`='Note' WHERE `tabNote`.`name`='Test Note Title'".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

		self.assertEqual(
			jingrow.qb.get_query(
				"PageType",
				fields=["name", "module.app_name as app_name"],
			).get_sql(),
			"SELECT `tabPageType`.`name`,`tabModule Def`.`app_name` `app_name` FROM `tabPageType` LEFT JOIN `tabModule Def` ON `tabModule Def`.`name`=`tabPageType`.`module`".replace(
				"`", '"' if jingrow.db.db_type == "postgres" else "`"
			),
		)

	@run_only_if(db_type_is.MARIADB)
	def test_comment_stripping(self):
		self.assertNotIn(
			"email", jingrow.qb.get_query("User", fields=["name", "#email"], filters={}).get_sql()
		)

	def test_nestedset(self):
		jingrow.db.sql("delete from `tabPageType` where `name` = 'Test Tree PageType'")
		jingrow.db.sql_ddl("drop table if exists `tabTest Tree PageType`")
		create_tree_pgs()
		descendants_result = jingrow.qb.get_query(
			"Test Tree PageType",
			fields=["name"],
			filters={"name": ("descendants of", "Parent 1")},
			order_by="modified desc",
		).run(as_list=1)

		# Format decendants result
		descendants_result = list(itertools.chain.from_iterable(descendants_result))
		self.assertListEqual(descendants_result, get_descendants_of("Test Tree PageType", "Parent 1"))

		ancestors_result = jingrow.qb.get_query(
			"Test Tree PageType",
			fields=["name"],
			filters={"name": ("ancestors of", "Child 2")},
			order_by="modified desc",
		).run(as_list=1)

		# Format ancestors result
		ancestors_result = list(itertools.chain.from_iterable(ancestors_result))
		self.assertListEqual(ancestors_result, get_ancestors_of("Test Tree PageType", "Child 2"))

		not_descendants_result = jingrow.qb.get_query(
			"Test Tree PageType",
			fields=["name"],
			filters={"name": ("not descendants of", "Parent 1")},
			order_by="modified desc",
		).run(as_dict=1)

		self.assertListEqual(
			not_descendants_result,
			jingrow.db.get_all(
				"Test Tree PageType",
				fields=["name"],
				filters={"name": ("not descendants of", "Parent 1")},
			),
		)

		not_ancestors_result = jingrow.qb.get_query(
			"Test Tree PageType",
			fields=["name"],
			filters={"name": ("not ancestors of", "Child 2")},
			order_by="modified desc",
		).run(as_dict=1)

		self.assertListEqual(
			not_ancestors_result,
			jingrow.db.get_all(
				"Test Tree PageType",
				fields=["name"],
				filters={"name": ("not ancestors of", "Child 2")},
			),
		)

		jingrow.db.sql("delete from `tabPageType` where `name` = 'Test Tree PageType'")
		jingrow.db.sql_ddl("drop table if exists `tabTest Tree PageType`")

	def test_child_field_syntax(self):
		note1 = jingrow.get_pg(pagetype="Note", title="Note 1", seen_by=[{"user": "Administrator"}]).insert()
		note2 = jingrow.get_pg(
			pagetype="Note", title="Note 2", seen_by=[{"user": "Administrator"}, {"user": "Guest"}]
		).insert()

		result = jingrow.qb.get_query(
			"Note",
			filters={"name": ["in", [note1.name, note2.name]]},
			fields=["name", {"seen_by": ["*"]}],
			order_by="title asc",
		).run(as_dict=1)

		self.assertTrue(isinstance(result[0].seen_by, list))
		self.assertTrue(isinstance(result[1].seen_by, list))
		self.assertEqual(len(result[0].seen_by), 1)
		self.assertEqual(len(result[1].seen_by), 2)
		self.assertEqual(result[0].seen_by[0].user, "Administrator")

		result = jingrow.qb.get_query(
			"Note",
			filters={"name": ["in", [note1.name, note2.name]]},
			fields=["name", {"seen_by": ["user"]}],
			order_by="title asc",
		).run(as_dict=1)

		self.assertEqual(len(result[0].seen_by[0].keys()), 1)
		self.assertEqual(result[1].seen_by[1].user, "Guest")

		note1.delete()
		note2.delete()
