import jingrow
from jingrow.core.pagetype.pagetype.test_pagetype import new_pagetype
from jingrow.core.utils import find
from jingrow.custom.pagetype.property_setter.property_setter import make_property_setter
from jingrow.query_builder.utils import db_type_is
from jingrow.tests.test_query_builder import run_only_if
from jingrow.tests.utils import JingrowTestCase
from jingrow.utils import cstr


class TestDBUpdate(JingrowTestCase):
	def test_db_update(self):
		pagetype = "User"
		jingrow.reload_pagetype("User", force=True)
		jingrow.model.meta.trim_tables("User")
		make_property_setter(pagetype, "bio", "fieldtype", "Text", "Data")
		make_property_setter(pagetype, "middle_name", "fieldtype", "Data", "Text")
		make_property_setter(pagetype, "enabled", "default", "1", "Int")

		jingrow.db.updatedb(pagetype)

		field_defs = get_field_defs(pagetype)
		table_columns = jingrow.db.get_table_columns_description(f"tab{pagetype}")

		self.assertEqual(len(field_defs), len(table_columns))

		for field_def in field_defs:
			fieldname = field_def.get("fieldname")
			table_column = find(table_columns, lambda d: d.get("name") == fieldname)

			fieldtype = get_fieldtype_from_def(field_def)

			fallback_default = (
				"0" if field_def.get("fieldtype") in jingrow.model.numeric_fieldtypes else "NULL"
			)
			default = field_def.default if field_def.default is not None else fallback_default

			self.assertEqual(fieldtype, table_column.type)
			self.assertIn(cstr(table_column.default) or "NULL", [cstr(default), f"'{default}'"])

	def test_index_and_unique_constraints(self):
		pagetype = "User"
		jingrow.reload_pagetype("User", force=True)
		jingrow.model.meta.trim_tables("User")

		make_property_setter(pagetype, "middle_name", "unique", "1", "Check")
		jingrow.db.updatedb(pagetype)
		middle_name_in_table = get_table_column("User", "middle_name")
		self.assertTrue(middle_name_in_table.unique)

		make_property_setter(pagetype, "middle_name", "unique", "0", "Check")
		jingrow.db.updatedb(pagetype)
		middle_name_in_table = get_table_column("User", "middle_name")
		self.assertFalse(middle_name_in_table.unique)

		make_property_setter(pagetype, "middle_name", "search_index", "1", "Check")
		jingrow.db.updatedb(pagetype)
		middle_name_in_table = get_table_column("User", "middle_name")
		self.assertTrue(middle_name_in_table.index)

		make_property_setter(pagetype, "middle_name", "search_index", "0", "Check")
		jingrow.db.updatedb(pagetype)
		middle_name_in_table = get_table_column("User", "middle_name")
		self.assertFalse(middle_name_in_table.index)

		make_property_setter(pagetype, "middle_name", "search_index", "1", "Check")
		make_property_setter(pagetype, "middle_name", "unique", "1", "Check")
		jingrow.db.updatedb(pagetype)
		middle_name_in_table = get_table_column("User", "middle_name")
		self.assertTrue(middle_name_in_table.index)
		self.assertTrue(middle_name_in_table.unique)

		make_property_setter(pagetype, "middle_name", "search_index", "1", "Check")
		make_property_setter(pagetype, "middle_name", "unique", "0", "Check")
		jingrow.db.updatedb(pagetype)
		middle_name_in_table = get_table_column("User", "middle_name")
		self.assertTrue(middle_name_in_table.index)
		self.assertFalse(middle_name_in_table.unique)

		make_property_setter(pagetype, "middle_name", "search_index", "0", "Check")
		make_property_setter(pagetype, "middle_name", "unique", "1", "Check")
		jingrow.db.updatedb(pagetype)
		middle_name_in_table = get_table_column("User", "middle_name")
		self.assertFalse(middle_name_in_table.index)
		self.assertTrue(middle_name_in_table.unique)

		# explicitly make a text index
		jingrow.db.add_index(pagetype, ["email_signature(200)"])
		jingrow.db.updatedb(pagetype)
		email_sig_column = get_table_column("User", "email_signature")
		self.assertEqual(email_sig_column.index, 1)

	def check_unique_indexes(self, pagetype: str, field: str):
		indexes = jingrow.db.sql(
			f"""show index from `tab{pagetype}` where column_name = '{field}' and Non_unique = 0""",
			as_dict=1,
		)
		self.assertEqual(
			len(indexes), 1, msg=f"There should be 1 index on {pagetype}.{field}, found {indexes}"
		)

	@run_only_if(db_type_is.MARIADB)  # postgres uses invalid type for <=15
	def test_bigint_conversion(self):
		pagetype = new_pagetype(fields=[{"fieldname": "int_field", "fieldtype": "Int"}]).insert()

		with self.assertRaises(jingrow.CharacterLengthExceededError):
			jingrow.get_pg(pagetype=pagetype.name, int_field=2**62 - 1).insert()

		pagetype.fields[0].length = 14
		pagetype.save()
		jingrow.get_pg(pagetype=pagetype.name, int_field=2**62 - 1).insert()

	@run_only_if(db_type_is.MARIADB)
	def test_unique_index_on_install(self):
		"""Only one unique index should be added"""
		for dt in jingrow.get_all("PageType", {"is_virtual": 0, "issingle": 0}, pluck="name"):
			pagetype = jingrow.get_meta(dt)
			fields = pagetype.get("fields", filters={"unique": 1})
			for field in fields:
				with self.subTest(f"Checking index {pagetype.name} - {field.fieldname}"):
					self.check_unique_indexes(pagetype.name, field.fieldname)

	@run_only_if(db_type_is.MARIADB)
	def test_unique_index_on_alter(self):
		"""Only one unique index should be added"""

		pagetype = new_pagetype(unique=1).insert()
		field = "some_fieldname"

		self.check_unique_indexes(pagetype.name, field)
		pagetype.fields[0].length = 142
		pagetype.save()
		self.check_unique_indexes(pagetype.name, field)

		pagetype.fields[0].unique = 0
		pagetype.save()

		pagetype.fields[0].unique = 1
		pagetype.save()
		self.check_unique_indexes(pagetype.name, field)

		# New column with a unique index
		# This works because index name is same as fieldname.
		new_field = jingrow.copy_pg(pagetype.fields[0])
		new_field.fieldname = "duplicate_field"
		pagetype.append("fields", new_field)
		pagetype.save()
		self.check_unique_indexes(pagetype.name, new_field.fieldname)

		pagetype.delete()
		jingrow.db.commit()


def get_fieldtype_from_def(field_def):
	fieldtuple = jingrow.db.type_map.get(field_def.fieldtype, ("", 0))
	fieldtype = fieldtuple[0]
	if fieldtype in ("varchar", "datetime", "int"):
		fieldtype += f"({field_def.length or fieldtuple[1]})"
	return fieldtype


def get_field_defs(pagetype):
	meta = jingrow.get_meta(pagetype, cached=False)
	field_defs = meta.get_fieldnames_with_value(True)
	field_defs += get_other_fields_meta(meta)
	return field_defs


def get_other_fields_meta(meta):
	default_fields_map = {
		"name": ("Data", 0),
		"owner": ("Data", 0),
		"modified_by": ("Data", 0),
		"creation": ("Datetime", 0),
		"modified": ("Datetime", 0),
		"idx": ("Int", 8),
		"pagestatus": ("Check", 0),
	}

	optional_fields = jingrow.db.OPTIONAL_COLUMNS
	if meta.track_seen:
		optional_fields.append("_seen")

	child_table_fields_map = {}
	if meta.istable:
		child_table_fields_map.update({field: ("Data", 0) for field in jingrow.db.CHILD_TABLE_COLUMNS})

	optional_fields_map = {field: ("Text", 0) for field in optional_fields}
	fields = dict(default_fields_map, **optional_fields_map, **child_table_fields_map)
	return [
		jingrow._dict({"fieldname": field, "fieldtype": _type, "length": _length})
		for field, (_type, _length) in fields.items()
	]


def get_table_column(pagetype, fieldname):
	table_columns = jingrow.db.get_table_columns_description(f"tab{pagetype}")
	return find(table_columns, lambda d: d.get("name") == fieldname)
