# Copyright (c) 2015, JINGROW and Contributors
# License: MIT. See LICENSE
import os
import random
import string
import unittest
from unittest.mock import patch

import jingrow
from jingrow.cache_manager import clear_pagetype_cache
from jingrow.core.pagetype.pagetype.pagetype import (
	CannotIndexedError,
	PageType,
	PagetypeLinkError,
	HiddenAndMandatoryWithoutDefaultError,
	IllegalMandatoryError,
	InvalidFieldNameError,
	UniqueFieldnameError,
	WrongOptionsPagetypeLinkError,
	validate_links_table_fieldnames,
)
from jingrow.core.pagetype.rq_job.test_rq_job import wait_for_completion
from jingrow.custom.pagetype.custom_field.custom_field import create_custom_fields
from jingrow.desk.form.load import getpage
from jingrow.model.delete_pg import delete_controllers
from jingrow.model.sync import remove_orphan_pagetypes
from jingrow.tests.utils import JingrowTestCase
from jingrow.utils import get_table_name


class TestPageType(JingrowTestCase):
	def tearDown(self):
		jingrow.db.rollback()

	def test_validate_name(self):
		self.assertRaises(jingrow.NameError, new_pagetype("_Some PageType").insert)
		self.assertRaises(jingrow.NameError, new_pagetype("8Some PageType").insert)
		self.assertRaises(jingrow.NameError, new_pagetype("Some (PageType)").insert)
		self.assertRaises(
			jingrow.NameError,
			new_pagetype("Some Pagetype with a name whose length is more than 61 characters").insert,
		)
		for name in ("Some PageType", "Some_PageType", "Some-PageType"):
			if jingrow.db.exists("PageType", name):
				jingrow.delete_pg("PageType", name)

			pg = new_pagetype(name).insert()
			pg.delete()

	def test_making_sequence_on_change(self):
		jingrow.delete_pg_if_exists("PageType", self._testMethodName)
		dt = new_pagetype(self._testMethodName).insert(ignore_permissions=True)
		autoname = dt.autoname

		# change autoname
		dt.autoname = "autoincrement"
		dt.save()

		# check if name type has been changed
		self.assertEqual(
			jingrow.db.sql(
				f"""select data_type FROM information_schema.columns
				where column_name = 'name' and table_name = 'tab{self._testMethodName}'"""
			)[0][0],
			"bigint",
		)

		if jingrow.db.db_type == "mariadb":
			table_name = "information_schema.tables"
			conditions = f"table_type = 'sequence' and table_name = '{self._testMethodName}_id_seq'"
		else:
			table_name = "information_schema.sequences"
			conditions = f"sequence_name = '{self._testMethodName}_id_seq'"

		# check if sequence table is created
		self.assertTrue(
			jingrow.db.sql(
				f"""select * from {table_name}
				where {conditions}"""
			)
		)

		# change the autoname/naming rule back to original
		dt.autoname = autoname
		dt.save()

		# check if name type has changed
		self.assertEqual(
			jingrow.db.sql(
				f"""select data_type FROM information_schema.columns
				where column_name = 'name' and table_name = 'tab{self._testMethodName}'"""
			)[0][0],
			"varchar" if jingrow.db.db_type == "mariadb" else "character varying",
		)

	def test_pagetype_unique_constraint_dropped(self):
		if jingrow.db.exists("PageType", "With_Unique"):
			jingrow.delete_pg("PageType", "With_Unique")

		dt = new_pagetype("With_Unique", unique=1)
		dt.insert()

		doc1 = jingrow.new_pg("With_Unique")
		doc2 = jingrow.new_pg("With_Unique")
		doc1.some_fieldname = "Something"
		doc1.name = "one"
		doc2.some_fieldname = "Something"
		doc2.name = "two"

		doc1.insert()
		self.assertRaises(jingrow.UniqueValidationError, doc2.insert)
		jingrow.db.rollback()

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

		doc2.insert()
		doc1.delete()
		doc2.delete()

	def test_validate_search_fields(self):
		pg = new_pagetype("Test Search Fields")
		pg.search_fields = "some_fieldname"
		pg.insert()
		self.assertEqual(pg.name, "Test Search Fields")

		# check if invalid fieldname is allowed or not
		pg.search_fields = "some_fieldname_1"
		self.assertRaises(jingrow.ValidationError, pg.save)

		# check if no value fields are allowed in search fields
		field = pg.append("fields", {})
		field.fieldname = "some_html_field"
		field.fieldtype = "HTML"
		field.label = "Some HTML Field"
		pg.search_fields = "some_fieldname,some_html_field"
		self.assertRaises(jingrow.ValidationError, pg.save)

	def test_depends_on_fields(self):
		pg = new_pagetype("Test Depends On", depends_on="eval:pg.__islocal == 0")
		pg.insert()

		# check if the assignment operation is allowed in depends_on
		field = pg.fields[0]
		field.depends_on = "eval:pg.__islocal = 0"
		self.assertRaises(jingrow.ValidationError, pg.save)

	def test_all_depends_on_fields_conditions(self):
		import re

		pagefields = jingrow.get_all(
			"PageField",
			or_filters={
				"ifnull(depends_on, '')": ("!=", ""),
				"ifnull(collapsible_depends_on, '')": ("!=", ""),
				"ifnull(mandatory_depends_on, '')": ("!=", ""),
				"ifnull(read_only_depends_on, '')": ("!=", ""),
			},
			fields=[
				"parent",
				"depends_on",
				"collapsible_depends_on",
				"mandatory_depends_on",
				"read_only_depends_on",
				"fieldname",
				"fieldtype",
			],
		)

		pattern = r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+'
		for field in pagefields:
			for depends_on in [
				"depends_on",
				"collapsible_depends_on",
				"mandatory_depends_on",
				"read_only_depends_on",
			]:
				condition = field.get(depends_on)
				if condition:
					self.assertFalse(re.match(pattern, condition))

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	def test_sync_field_order(self):
		import os

		from jingrow.modules.import_file import get_file_path

		# create test pagetype
		test_pagetype = jingrow.get_pg(
			{
				"pagetype": "PageType",
				"module": "Core",
				"fields": [
					{"label": "Field 1", "fieldname": "field_1", "fieldtype": "Data"},
					{"label": "Field 2", "fieldname": "field_2", "fieldtype": "Data"},
					{"label": "Field 3", "fieldname": "field_3", "fieldtype": "Data"},
					{"label": "Field 4", "fieldname": "field_4", "fieldtype": "Data"},
				],
				"permissions": [{"role": "System Manager", "read": 1}],
				"name": "Test Field Order PageType",
				"__islocal": 1,
			}
		)

		path = get_file_path(test_pagetype.module, test_pagetype.pagetype, test_pagetype.name)
		initial_fields_order = ["field_1", "field_2", "field_3", "field_4"]

		jingrow.delete_pg_if_exists("PageType", "Test Field Order PageType")
		if os.path.isfile(path):
			os.remove(path)

		try:
			jingrow.flags.allow_pagetype_export = 1
			test_pagetype.save()

			# assert that field_order list is being created with the default order
			test_pagetype_json = jingrow.get_file_json(path)
			self.assertTrue(test_pagetype_json.get("field_order"))
			self.assertEqual(len(test_pagetype_json["fields"]), len(test_pagetype_json["field_order"]))
			self.assertListEqual(
				[f["fieldname"] for f in test_pagetype_json["fields"]], test_pagetype_json["field_order"]
			)
			self.assertListEqual([f["fieldname"] for f in test_pagetype_json["fields"]], initial_fields_order)
			self.assertListEqual(test_pagetype_json["field_order"], initial_fields_order)

			# remove field_order to test reload_pg/sync/migrate is backwards compatible without field_order
			del test_pagetype_json["field_order"]
			with open(path, "w+") as txtfile:
				txtfile.write(jingrow.as_json(test_pagetype_json))

			# assert that field_order is actually removed from the json file
			test_pagetype_json = jingrow.get_file_json(path)
			self.assertFalse(test_pagetype_json.get("field_order"))

			# make sure that migrate/sync is backwards compatible without field_order
			jingrow.reload_pagetype(test_pagetype.name, force=True)
			test_pagetype.reload()

			# assert that field_order list is being created with the default order again
			test_pagetype.save()
			test_pagetype_json = jingrow.get_file_json(path)
			self.assertTrue(test_pagetype_json.get("field_order"))
			self.assertEqual(len(test_pagetype_json["fields"]), len(test_pagetype_json["field_order"]))
			self.assertListEqual(
				[f["fieldname"] for f in test_pagetype_json["fields"]], test_pagetype_json["field_order"]
			)
			self.assertListEqual([f["fieldname"] for f in test_pagetype_json["fields"]], initial_fields_order)
			self.assertListEqual(test_pagetype_json["field_order"], initial_fields_order)

			# reorder fields: swap row 1 and 3
			test_pagetype.fields[0], test_pagetype.fields[2] = test_pagetype.fields[2], test_pagetype.fields[0]
			for i, f in enumerate(test_pagetype.fields):
				f.idx = i + 1

			# assert that reordering fields only affects `field_order` rather than `fields` attr
			test_pagetype.save()
			test_pagetype_json = jingrow.get_file_json(path)
			self.assertListEqual([f["fieldname"] for f in test_pagetype_json["fields"]], initial_fields_order)
			self.assertListEqual(
				test_pagetype_json["field_order"], ["field_3", "field_2", "field_1", "field_4"]
			)

			# reorder `field_order` in the json file: swap row 2 and 4
			test_pagetype_json["field_order"][1], test_pagetype_json["field_order"][3] = (
				test_pagetype_json["field_order"][3],
				test_pagetype_json["field_order"][1],
			)
			with open(path, "w+") as txtfile:
				txtfile.write(jingrow.as_json(test_pagetype_json))

			# assert that reordering `field_order` from json file is reflected in PageType upon migrate/sync
			jingrow.reload_pagetype(test_pagetype.name, force=True)
			test_pagetype.reload()
			self.assertListEqual(
				[f.fieldname for f in test_pagetype.fields], ["field_3", "field_4", "field_1", "field_2"]
			)

			# insert row in the middle and remove first row (field 3)
			test_pagetype.append("fields", {"label": "Field 5", "fieldname": "field_5", "fieldtype": "Data"})
			test_pagetype.fields[4], test_pagetype.fields[3] = test_pagetype.fields[3], test_pagetype.fields[4]
			test_pagetype.fields[3], test_pagetype.fields[2] = test_pagetype.fields[2], test_pagetype.fields[3]
			test_pagetype.remove(test_pagetype.fields[0])
			for i, f in enumerate(test_pagetype.fields):
				f.idx = i + 1

			test_pagetype.save()
			test_pagetype_json = jingrow.get_file_json(path)
			self.assertListEqual(
				[f["fieldname"] for f in test_pagetype_json["fields"]],
				["field_1", "field_2", "field_4", "field_5"],
			)
			self.assertListEqual(
				test_pagetype_json["field_order"], ["field_4", "field_5", "field_1", "field_2"]
			)
		except Exception:
			raise
		finally:
			jingrow.flags.allow_pagetype_export = 0

	def test_unique_field_name_for_two_fields(self):
		pg = new_pagetype("Test Unique Field")
		field_1 = pg.append("fields", {})
		field_1.fieldname = "some_fieldname_1"
		field_1.fieldtype = "Data"

		field_2 = pg.append("fields", {})
		field_2.fieldname = "some_fieldname_1"
		field_2.fieldtype = "Data"

		self.assertRaises(UniqueFieldnameError, pg.insert)

	def test_fieldname_is_not_name(self):
		pg = new_pagetype("Test Name Field")
		field_1 = pg.append("fields", {})
		field_1.label = "Name"
		field_1.fieldtype = "Data"
		pg.insert()
		self.assertEqual(pg.fields[1].fieldname, "name1")
		pg.fields[1].fieldname = "name"
		self.assertRaises(InvalidFieldNameError, pg.save)

	def test_illegal_mandatory_validation(self):
		pg = new_pagetype("Test Illegal mandatory")
		field_1 = pg.append("fields", {})
		field_1.fieldname = "some_fieldname_1"
		field_1.fieldtype = "Section Break"
		field_1.reqd = 1

		self.assertRaises(IllegalMandatoryError, pg.insert)

	def test_link_with_wrong_and_no_options(self):
		pg = new_pagetype("Test link")
		field_1 = pg.append("fields", {})
		field_1.fieldname = "some_fieldname_1"
		field_1.fieldtype = "Link"

		self.assertRaises(PagetypeLinkError, pg.insert)

		field_1.options = "wrongdoctype"

		self.assertRaises(WrongOptionsPagetypeLinkError, pg.insert)

	def test_hidden_and_mandatory_without_default(self):
		pg = new_pagetype("Test hidden and mandatory")
		field_1 = pg.append("fields", {})
		field_1.fieldname = "some_fieldname_1"
		field_1.fieldtype = "Data"
		field_1.reqd = 1
		field_1.hidden = 1

		self.assertRaises(HiddenAndMandatoryWithoutDefaultError, pg.insert)

	def test_field_can_not_be_indexed_validation(self):
		pg = new_pagetype("Test index")
		field_1 = pg.append("fields", {})
		field_1.fieldname = "some_fieldname_1"
		field_1.fieldtype = "Long Text"
		field_1.search_index = 1

		self.assertRaises(CannotIndexedError, pg.insert)

	def test_cancel_link_pagetype(self):
		import json

		from jingrow.desk.form.linked_with import cancel_all_linked_pgs, get_submitted_linked_pgs

		# create pagetype
		link_pg = new_pagetype("Test Linked Pagetype")
		link_pg.is_submittable = 1
		for data in link_pg.get("permissions"):
			data.submit = 1
			data.cancel = 1
		link_pg.insert()

		pg = new_pagetype("Test Pagetype")
		pg.is_submittable = 1
		field_2 = pg.append("fields", {})
		field_2.label = "Test Linked Pagetype"
		field_2.fieldname = "test_linked_pagetype"
		field_2.fieldtype = "Link"
		field_2.options = "Test Linked Pagetype"
		for data in link_pg.get("permissions"):
			data.submit = 1
			data.cancel = 1
		pg.insert()

		# create pagetype data
		data_link_pg = jingrow.new_pg("Test Linked Pagetype")
		data_link_pg.some_fieldname = "Data1"
		data_link_pg.insert()
		data_link_pg.save()
		data_link_pg.submit()

		data_pg = jingrow.new_pg("Test Pagetype")
		data_pg.some_fieldname = "Data1"
		data_pg.test_linked_pagetype = data_link_pg.name
		data_pg.insert()
		data_pg.save()
		data_pg.submit()

		pgs = get_submitted_linked_pgs(link_pg.name, data_link_pg.name)
		dump_pgs = json.dumps(pgs.get("pgs"))
		cancel_all_linked_pgs(dump_pgs)
		data_link_pg.cancel()
		data_pg.load_from_db()
		self.assertEqual(data_link_pg.pagestatus, 2)
		self.assertEqual(data_pg.pagestatus, 2)

		# delete pagetype record
		data_pg.delete()
		data_link_pg.delete()

		# delete pagetype
		link_pg.delete()
		pg.delete()
		jingrow.db.commit()

	def test_ignore_cancelation_of_linked_pagetype_during_cancel(self):
		import json

		from jingrow.desk.form.linked_with import cancel_all_linked_pgs, get_submitted_linked_pgs

		# create linked pagetype
		link_pg = new_pagetype("Test Linked Pagetype 1")
		link_pg.is_submittable = 1
		for data in link_pg.get("permissions"):
			data.submit = 1
			data.cancel = 1
		link_pg.insert()

		# create first parent pagetype
		test_pg_1 = new_pagetype("Test Pagetype 1")
		test_pg_1.is_submittable = 1

		field_2 = test_pg_1.append("fields", {})
		field_2.label = "Test Linked Pagetype 1"
		field_2.fieldname = "test_linked_pagetype_a"
		field_2.fieldtype = "Link"
		field_2.options = "Test Linked Pagetype 1"

		for data in test_pg_1.get("permissions"):
			data.submit = 1
			data.cancel = 1
		test_pg_1.insert()

		# crete second parent pagetype
		pg = new_pagetype("Test Pagetype 2")
		pg.is_submittable = 1

		field_2 = pg.append("fields", {})
		field_2.label = "Test Linked Pagetype 1"
		field_2.fieldname = "test_linked_pagetype_a"
		field_2.fieldtype = "Link"
		field_2.options = "Test Linked Pagetype 1"

		for data in link_pg.get("permissions"):
			data.submit = 1
			data.cancel = 1
		pg.insert()

		# create pagetype data
		data_link_pg_1 = jingrow.new_pg("Test Linked Pagetype 1")
		data_link_pg_1.some_fieldname = "Data1"
		data_link_pg_1.insert()
		data_link_pg_1.save()
		data_link_pg_1.submit()

		data_pg_2 = jingrow.new_pg("Test Pagetype 1")
		data_pg_2.some_fieldname = "Data1"
		data_pg_2.test_linked_pagetype_a = data_link_pg_1.name
		data_pg_2.insert()
		data_pg_2.save()
		data_pg_2.submit()

		data_pg = jingrow.new_pg("Test Pagetype 2")
		data_pg.some_fieldname = "Data1"
		data_pg.test_linked_pagetype_a = data_link_pg_1.name
		data_pg.insert()
		data_pg.save()
		data_pg.submit()

		pgs = get_submitted_linked_pgs(link_pg.name, data_link_pg_1.name)
		dump_pgs = json.dumps(pgs.get("pgs"))

		cancel_all_linked_pgs(dump_pgs, ignore_pagetypes_on_cancel_all=["Test Pagetype 2"])

		# checking that pg for Test Pagetype 2 is not canceled
		self.assertRaises(jingrow.LinkExistsError, data_link_pg_1.cancel)

		data_pg.load_from_db()
		data_pg_2.load_from_db()
		self.assertEqual(data_link_pg_1.pagestatus, 2)

		# linked pg is canceled
		self.assertEqual(data_pg_2.pagestatus, 2)

		# ignored pagetype 2 during cancel
		self.assertEqual(data_pg.pagestatus, 1)

		# delete pagetype record
		data_pg.cancel()
		data_pg.delete()
		data_pg_2.delete()
		data_link_pg_1.delete()

		# delete pagetype
		link_pg.delete()
		pg.delete()
		test_pg_1.delete()
		jingrow.db.commit()

	def test_links_table_fieldname_validation(self):
		pg = new_pagetype("Test Links Table Validation")

		# check valid data
		pg.append("links", {"link_pagetype": "User", "link_fieldname": "first_name"})
		validate_links_table_fieldnames(pg)  # no error
		pg.links = []  # reset links table

		# check invalid pagetype
		pg.append("links", {"link_pagetype": "User2", "link_fieldname": "first_name"})
		self.assertRaises(InvalidFieldNameError, validate_links_table_fieldnames, pg)
		pg.links = []  # reset links table

		# check invalid fieldname
		pg.append("links", {"link_pagetype": "User", "link_fieldname": "a_field_that_does_not_exists"})

		self.assertRaises(InvalidFieldNameError, validate_links_table_fieldnames, pg)

	def test_create_virtual_pagetype(self):
		"""Test virtual PageType."""
		virtual_pg = new_pagetype("Test Virtual Pagetype")
		virtual_pg.is_virtual = 1
		virtual_pg.insert(ignore_if_duplicate=True)
		virtual_pg.reload()
		pg = jingrow.get_pg("PageType", "Test Virtual Pagetype")

		self.assertDictEqual(pg.as_dict(), virtual_pg.as_dict())
		self.assertEqual(pg.is_virtual, 1)
		self.assertFalse(jingrow.db.table_exists("Test Virtual Pagetype"))

	def test_create_virtual_pagetype_as_child_table(self):
		"""Test virtual PageType as Child Table below a normal PageType."""
		jingrow.delete_pg_if_exists("PageType", "Test Parent Virtual PageType", force=1)
		jingrow.delete_pg_if_exists("PageType", "Test Virtual PageType as Child Table", force=1)

		virtual_pg = new_pagetype("Test Virtual PageType as Child Table")
		virtual_pg.is_virtual = 1
		virtual_pg.istable = 1
		virtual_pg.insert(ignore_permissions=True)

		pg = jingrow.get_pg("PageType", "Test Virtual PageType as Child Table")

		self.assertEqual(pg.is_virtual, 1)
		self.assertEqual(pg.istable, 1)
		self.assertFalse(jingrow.db.table_exists("Test Virtual PageType as Child Table"))

		parent_pg = new_pagetype("Test Parent Virtual PageType")
		parent_pg.append(
			"fields",
			{
				"fieldname": "virtual_child_table",
				"fieldtype": "Table",
				"options": "Test Virtual PageType as Child Table",
			},
		)
		self.assertRaises(jingrow.exceptions.ValidationError, parent_pg.insert)
		parent_pg.is_virtual = 1
		parent_pg.insert(ignore_permissions=True)
		self.assertFalse(jingrow.db.table_exists("Test Parent Virtual PageType"))

	def test_default_fieldname(self):
		fields = [
			{"label": "title", "fieldname": "title", "fieldtype": "Data", "default": "{some_fieldname}"}
		]
		dt = new_pagetype("DT with default field", fields=fields)
		dt.insert()

		dt.delete()

	def test_autoincremented_pagetype_transition(self):
		jingrow.delete_pg_if_exists("PageType", "testy_autoinc_dt")
		dt = new_pagetype("testy_autoinc_dt", autoname="autoincrement").insert(ignore_permissions=True)
		dt.autoname = "hash"

		dt.save(ignore_permissions=True)

		dt_data = jingrow.get_pg({"pagetype": dt.name, "some_fieldname": "test data"}).insert(
			ignore_permissions=True
		)

		dt.autoname = "autoincrement"

		try:
			dt.save(ignore_permissions=True)
		except jingrow.ValidationError as e:
			self.assertEqual(
				e.args[0],
				"Can only change to/from Autoincrement naming rule when there is no data in the pagetype",
			)
		else:
			self.fail(
				"""Shouldn't be possible to transition to/from autoincremented pagetype
				when data is present in pagetype"""
			)
		finally:
			# cleanup
			dt_data.delete(ignore_permissions=True)
			dt.delete(ignore_permissions=True)

	def test_json_field(self):
		"""Test json field."""
		import json

		json_pg = new_pagetype(
			"Test Json Pagetype",
			fields=[{"label": "json field", "fieldname": "test_json_field", "fieldtype": "JSON"}],
		)
		json_pg.insert()
		json_pg.save()
		pg = jingrow.get_pg("PageType", "Test Json Pagetype")
		for field in pg.fields:
			if field.fieldname == "test_json_field":
				self.assertEqual(field.fieldtype, "JSON")
				break

		pg = jingrow.get_pg(
			{"pagetype": "Test Json Pagetype", "test_json_field": json.dumps({"hello": "world"})}
		)
		pg.insert()
		pg.save()

		test_json = jingrow.get_pg("Test Json Pagetype", pg.name)

		if isinstance(test_json.test_json_field, str):
			test_json.test_json_field = json.loads(test_json.test_json_field)

		self.assertEqual(test_json.test_json_field["hello"], "world")

	def test_no_delete_pg(self):
		self.assertRaises(jingrow.ValidationError, jingrow.delete_pg, "PageType", "Address")

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	@patch.dict(jingrow.conf, {"developer_mode": 1})
	def test_export_types(self):
		"""Export python types."""
		import ast

		from jingrow.types.exporter import TypeExporter

		def validate(code):
			ast.parse(code)

		pagetype = new_pagetype(custom=0).insert()

		exporter = TypeExporter(pagetype)
		code = exporter.controller_path.read_text()
		validate(code)

		# regenerate and verify and file is same word to word.
		exporter.export_types()
		new_code = exporter.controller_path.read_text()
		validate(new_code)

		self.assertEqual(code, new_code)

		# Add fields and save

		fieldname = "test_type"
		pagetype.append("fields", {"fieldname": fieldname, "fieldtype": "Int"})
		pagetype.save()

		new_field_code = exporter.controller_path.read_text()
		validate(new_field_code)

		self.assertIn(fieldname, new_field_code)
		self.assertIn("Int", new_field_code)

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

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	@patch.dict(jingrow.conf, {"developer_mode": 1})
	def test_custom_field_deletion(self):
		"""Custom child tables whose pagetype doesn't exist should be auto deleted."""
		pagetype = new_pagetype(custom=0).insert().name
		child = new_pagetype(custom=0, istable=1).insert().name

		field = "abc"
		create_custom_fields({pagetype: [{"fieldname": field, "fieldtype": "Table", "options": child}]})

		jingrow.delete_pg("PageType", child)
		self.assertFalse(jingrow.get_meta(pagetype).get_field(field))

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	@patch.dict(jingrow.conf, {"developer_mode": 1})
	def test_delete_pagetype_with_customization(self):
		from jingrow.custom.pagetype.property_setter.property_setter import make_property_setter

		custom_field = "customfield"

		pagetype = new_pagetype(custom=0).insert().name

		# Create property setter and custom field
		field = "some_fieldname"
		make_property_setter(pagetype, field, "default", "DELETETHIS", "Data")
		create_custom_fields({pagetype: [{"fieldname": custom_field, "fieldtype": "Data"}]})

		# Create 1 record
		original_pg = jingrow.get_pg(pagetype=pagetype, custom_field_name="wat").insert()
		self.assertEqual(original_pg.some_fieldname, "DELETETHIS")

		# delete pagetype
		jingrow.delete_pg("PageType", pagetype)
		clear_pagetype_cache(pagetype)

		# "restore" pagetype by inserting pagetype with same schema again
		new_pagetype(pagetype, custom=0).insert()

		# Ensure basically same pagetype getting "restored"
		restored_pg = jingrow.get_last_pg(pagetype)
		verify_fields = ["pagetype", field, custom_field]
		for f in verify_fields:
			self.assertEqual(original_pg.get(f), restored_pg.get(f))

		# Check form load of restored pagetype
		getpage(pagetype, restored_pg.name)

		# ensure meta - property setter
		self.assertEqual(jingrow.get_meta(pagetype).get_field(field).default, "DELETETHIS")
		jingrow.delete_pg("PageType", pagetype)

	@unittest.skipUnless(
		os.access(jingrow.get_app_path("jingrow"), os.W_OK), "Only run if jingrow app paths is writable"
	)
	@patch.dict(jingrow.conf, {"developer_mode": 1})
	def test_delete_orphaned_pagetypes(self):
		pagetype = new_pagetype(custom=0).insert()
		jingrow.db.commit()

		delete_controllers(pagetype.name, pagetype.module)
		job = jingrow.enqueue(remove_orphan_pagetypes)
		wait_for_completion(job)

		jingrow.db.rollback()
		self.assertFalse(jingrow.db.exists("PageType", pagetype.name))

	def test_not_in_list_view_for_not_allowed_mandatory_field(self):
		pagetype = new_pagetype(
			fields=[
				{
					"fieldname": "cover_image",
					"fieldtype": "Attach Image",
					"label": "Cover Image",
					"reqd": 1,  # mandatory
				},
				{
					"fieldname": "book_name",
					"fieldtype": "Data",
					"label": "Book Name",
					"reqd": 1,  # mandatory
				},
			],
		).insert()

		self.assertFalse(pagetype.fields[0].in_list_view)
		self.assertTrue(pagetype.fields[1].in_list_view)
		jingrow.delete_pg("PageType", pagetype.name)

	def test_no_recursive_fetch(self):
		recursive_dt = new_pagetype(
			fields=[
				{
					"label": "User",
					"fieldname": "user",
					"fieldtype": "Link",
					"fetch_from": "user.email",
				}
			],
		)
		self.assertRaises(jingrow.ValidationError, recursive_dt.insert)

	def test_row_compression(self):
		if jingrow.db.db_type != "mariadb":
			return

		compressed_dt = new_pagetype(row_format="Compressed").insert().name
		dynamic_dt = new_pagetype().insert().name

		information_schema = jingrow.qb.Schema("information_schema")

		def get_format(dt):
			return (
				jingrow.qb.from_(information_schema.tables)
				.select("row_format")
				.where(
					(information_schema.tables.table_schema == jingrow.conf.db_name)
					& (information_schema.tables.table_name == get_table_name(dt))
				)
				.run()[0][0]
				.upper()
			)

		self.assertEqual(get_format(compressed_dt), "COMPRESSED")
		self.assertEqual(get_format(dynamic_dt), "DYNAMIC")


def new_pagetype(
	name: str | None = None,
	unique: bool = False,
	depends_on: str = "",
	fields: list[dict] | None = None,
	custom: bool = True,
	default: str | None = None,
	**kwargs,
) -> "PageType":
	if not name:
		# Test prefix is required to avoid coverage
		name = "Test " + "".join(random.sample(string.ascii_lowercase, 10))

	pg = jingrow.get_pg(
		{
			"pagetype": "PageType",
			"module": "Core",
			"custom": custom,
			"fields": [
				{
					"label": "Some Field",
					"fieldname": "some_fieldname",
					"fieldtype": "Data",
					"unique": unique,
					"default": default,
					"depends_on": depends_on,
				}
			],
			"permissions": [
				{
					"role": "System Manager",
					"read": 1,
				}
			],
			"name": name,
			**kwargs,
		}
	)

	if fields and len(fields) > 0:
		pg.set("fields", fields)

	return pg
