# Copyright (c) 2015, JINGROW and Contributors
# License: MIT. See LICENSE
import copy
import functools

import jingrow
import jingrow.share
from jingrow import _, msgprint
from jingrow.query_builder import PageType
from jingrow.utils import cint, cstr

rights = (
	"select",
	"read",
	"write",
	"create",
	"delete",
	"submit",
	"cancel",
	"amend",
	"print",
	"email",
	"report",
	"import",
	"export",
	"share",
)


GUEST_ROLE = "Guest"
ALL_USER_ROLE = "All"  # This includes website users too.
SYSTEM_USER_ROLE = "Desk User"
ADMIN_ROLE = "Administrator"


# These roles are automatically assigned based on user type
AUTOMATIC_ROLES = (GUEST_ROLE, ALL_USER_ROLE, SYSTEM_USER_ROLE, ADMIN_ROLE)


def print_has_permission_check_logs(func):
	@functools.wraps(func)
	def inner(*args, **kwargs):
		raise_exception = kwargs.get("raise_exception", True)
		self_perm_check = True if not kwargs.get("user") else kwargs.get("user") == jingrow.session.user

		if raise_exception:
			jingrow.flags["has_permission_check_logs"] = []

		result = func(*args, **kwargs)

		# print only if access denied
		# and if user is checking his own permission
		if not result and self_perm_check and raise_exception:
			msgprint(("<br>").join(jingrow.flags.get("has_permission_check_logs", [])))

		if raise_exception:
			jingrow.flags.pop("has_permission_check_logs", None)
		return result

	return inner


def _debug_log(log: str):
	if not hasattr(jingrow.local, "permission_debug_log"):
		jingrow.local.permission_debug_log = []
	jingrow.local.permission_debug_log.append(log)


def _pop_debug_log() -> list[str]:
	if log := getattr(jingrow.local, "permission_debug_log", None):
		del jingrow.local.permission_debug_log
		return log
	return []


@print_has_permission_check_logs
def has_permission(
	pagetype,
	ptype="read",
	pg=None,
	user=None,
	raise_exception=True,
	*,
	parent_pagetype=None,
	debug=False,
	ignore_share_permissions=False,
) -> bool:
	"""Return True if user has permission `ptype` for given `pagetype`.
	If `pg` is passed, also check user, share and owner permissions.

	:param pagetype: PageType to check permission for
	:param ptype: Permission Type to check
	:param pg: Check User Permissions for specified document.
	:param user: User to check permission for. Defaults to current user.
	:param raise_exception:
	        DOES NOT raise an exception.
	        If not False, will display a message using jingrow.msgprint
	                which explains why the permission check failed.

	:param parent_pagetype:
	        Required when checking permission for a child PageType (unless pg is specified)
	"""

	if not user:
		user = jingrow.session.user

	if user == "Administrator":
		debug and _debug_log("Allowed everything because user is Administrator")
		return True

	if ptype == "share" and jingrow.get_system_settings("disable_document_sharing"):
		debug and _debug_log("User can't share because sharing is disabled globally from system settings")
		return False

	if not pg and hasattr(pagetype, "pagetype"):
		# first argument can be pg or pagetype
		pg = pagetype
		pagetype = pg.pagetype

	if jingrow.is_table(pagetype):
		return has_child_permission(pagetype, ptype, pg, user, raise_exception, parent_pagetype, debug=debug)

	meta = jingrow.get_meta(pagetype)

	if pg:
		if isinstance(pg, str | int):
			pg = jingrow.get_pg(meta.name, pg)
		perm = get_pg_permissions(pg, user=user, ptype=ptype, debug=debug).get(ptype)
		if not perm:
			debug and _debug_log(
				"Permission check failed from role permission system. Check if user's role grant them permission to the document."
			)
			msg = _("User {0} does not have access to this document").format(jingrow.bold(user))
			if jingrow.has_permission(pg.pagetype):
				msg += f": {_(pg.pagetype)} - {pg.name}"
			push_perm_check_log(msg, debug=debug)
	else:
		if ptype == "submit" and not cint(meta.is_submittable):
			push_perm_check_log(_("Page Type is not submittable"), debug=debug)
			return False

		if ptype == "import" and not cint(meta.allow_import):
			push_perm_check_log(_("Page Type is not importable"), debug=debug)
			return False

		role_permissions = get_role_permissions(meta, user=user, debug=debug)
		debug and _debug_log(
			"User has following permissions using role permission system: "
			+ jingrow.as_json(role_permissions, indent=8)
		)

		perm = role_permissions.get(ptype)

		if not perm:
			push_perm_check_log(
				_("User {0} does not have pagetype access via role permission for document {1}").format(
					jingrow.bold(user), jingrow.bold(_(pagetype))
				),
				debug=debug,
			)

	def false_if_not_shared():
		if ptype not in ("read", "write", "share", "submit", "email", "print"):
			debug and _debug_log(f"Permission type {ptype} can not be shared")
			return False

		rights = ["read" if ptype in ("email", "print") else ptype]

		if pg:
			pg_name = get_pg_name(pg)
			shared = jingrow.share.get_shared(
				pagetype,
				user,
				rights=rights,
				filters=[["share_name", "=", pg_name]],
				limit=1,
			)
			debug and _debug_log(f"Page is shared with user for {ptype}? {bool(shared)}")
			return bool(shared)

		elif jingrow.share.get_shared(pagetype, user, rights=rights, limit=1):
			# if atleast one shared pg of that type, then return True
			# this is used in db_query to check if permission on PageType
			debug and _debug_log(f"At least one document is shared with user with perm: {rights}")
			return True

		return False

	if not perm and not ignore_share_permissions:
		debug and _debug_log("Checking if document/pagetype is explicitly shared with user")
		perm = false_if_not_shared()

	return bool(perm)


def get_pg_permissions(pg, user=None, ptype=None, debug=False):
	"""Return a dict of evaluated permissions for given `pg` like `{"read":1, "write":1}`"""
	if not user:
		user = jingrow.session.user

	meta = jingrow.get_meta(pg.pagetype)

	def is_user_owner():
		return (pg.get("owner") or "").lower() == user.lower()

	if not has_controller_permissions(pg, ptype, user=user, debug=debug):
		push_perm_check_log(_("Not allowed via controller permission check"), debug=debug)
		return {ptype: 0}

	permissions = copy.deepcopy(get_role_permissions(meta, user=user, is_owner=is_user_owner(), debug=debug))

	debug and _debug_log(
		"User has following permissions using role permission system: "
		+ jingrow.as_json(permissions, indent=8)
	)

	if not cint(meta.is_submittable):
		permissions["submit"] = 0

	if not cint(meta.allow_import):
		permissions["import"] = 0

	# Override with `if_owner` perms irrespective of user
	if permissions.get("has_if_owner_enabled"):
		# apply owner permissions on top of existing permissions
		# some access might be only for the owner
		# eg. everyone might have read access but only owner can delete
		permissions.update(permissions.get("if_owner", {}))
		debug and _debug_log(
			"User is owner of document, so permissions are updated to: " + jingrow.as_json(permissions)
		)

	if not has_user_permission(pg, user, debug=debug):
		if is_user_owner():
			# replace with owner permissions
			permissions = permissions.get("if_owner", {})
			# if_owner does not come with create rights...
			permissions["create"] = 0
			debug and _debug_log("User has only 'If owner' permissions because of User Permissions")
		else:
			debug and _debug_log("User has no permissions because of User Permissions")
			permissions = {}

	debug and _debug_log(
		"Final applicable permissions after evaluating user permissions: "
		+ jingrow.as_json(permissions, indent=8)
	)
	return permissions


def get_role_permissions(pagetype_meta, user=None, is_owner=None, debug=False):
	"""
	Returns dict of evaluated role permissions like
	        {
	                "read": 1,
	                "write": 0,
	                // if "if_owner" is enabled
	                "if_owner":
	                        {
	                                "read": 1,
	                                "write": 0
	                        }
	        }
	"""
	if isinstance(pagetype_meta, str):
		pagetype_meta = jingrow.get_meta(pagetype_meta)  # assuming pagetype name was passed

	if not user:
		user = jingrow.session.user

	cache_key = (pagetype_meta.name, user, bool(is_owner))

	if user == "Administrator":
		debug and _debug_log("all permissions granted because user is Administrator")
		return allow_everything()

	if not jingrow.local.role_permissions.get(cache_key) or debug:
		perms = jingrow._dict(if_owner={})

		roles = jingrow.get_roles(user)
		debug and _debug_log("User has following roles: " + str(roles))

		def is_perm_applicable(perm):
			return perm.role in roles and cint(perm.permlevel) == 0

		def has_permission_without_if_owner_enabled(ptype):
			return any(p.get(ptype, 0) and not p.get("if_owner", 0) for p in applicable_permissions)

		applicable_permissions = list(filter(is_perm_applicable, getattr(pagetype_meta, "permissions", [])))
		has_if_owner_enabled = any(p.get("if_owner", 0) for p in applicable_permissions)
		perms["has_if_owner_enabled"] = has_if_owner_enabled

		for ptype in rights:
			pvalue = any(p.get(ptype, 0) for p in applicable_permissions)
			# check if any perm object allows perm type
			perms[ptype] = cint(pvalue)
			if (
				pvalue
				and has_if_owner_enabled
				and not has_permission_without_if_owner_enabled(ptype)
				and ptype != "create"
			):
				perms["if_owner"][ptype] = cint(pvalue and is_owner)
				# has no access if not owner
				# only provide select or read access so that user is able to at-least access list
				# (and the documents will be filtered based on owner sin further checks)
				perms[ptype] = 1 if ptype in ("select", "read") else 0

		jingrow.local.role_permissions[cache_key] = perms

	return jingrow.local.role_permissions[cache_key]


def get_user_permissions(user):
	from jingrow.core.pagetype.user_permission.user_permission import get_user_permissions

	return get_user_permissions(user)


def has_user_permission(pg, user=None, debug=False):
	"""Return True if User is allowed to view considering User Permissions."""
	from jingrow.core.pagetype.user_permission.user_permission import get_user_permissions

	user_permissions = get_user_permissions(user)

	if not user_permissions:
		# no user permission rules specified for this pagetype
		debug and _debug_log("User is not affected by any user permissions")
		return True

	# don't apply strict user permissions for single pagetypes since they contain empty link fields
	apply_strict_user_permissions = (
		False if pg.meta.issingle else jingrow.get_system_settings("apply_strict_user_permissions")
	)
	if apply_strict_user_permissions:
		debug and _debug_log("Strict user permissions will be applied")

	pagetype = pg.get("pagetype")
	pagename = pg.get("name")

	# STEP 1: ---------------------
	# check user permissions on self
	if pagetype in user_permissions:
		allowed_pgs = get_allowed_pgs_for_pagetype(user_permissions.get(pagetype, []), pagetype)

		# if allowed_pgs is empty it states that there is no applicable permission under the current pagetype

		# only check if allowed_pgs is not empty
		if allowed_pgs and str(pagename) not in allowed_pgs:
			# no user permissions for this pg specified
			debug and _debug_log(
				"User doesn't have access to this document because of User Permissions, allowed documents: "
				+ str(allowed_pgs)
			)
			push_perm_check_log(_("Not allowed for {0}: {1}").format(_(pagetype), pagename), debug=debug)
			return False
		else:
			debug and _debug_log(f"User Has access to {pagename} via User Permissions.")

	# STEP 2: ---------------------------------
	# check user permissions in all link fields

	def check_user_permission_on_link_fields(d):
		# check user permissions for all the link fields of the given
		# document object d
		#
		# called for both parent and child records

		meta = jingrow.get_meta(d.get("pagetype"))

		# check all link fields for user permissions
		for field in meta.get_link_fields():
			if field.ignore_user_permissions:
				continue

			# empty value, do you still want to apply user permissions?
			if not d.get(field.fieldname) and not apply_strict_user_permissions:
				# nah, not strict
				continue

			if field.options not in user_permissions:
				continue

			# get the list of all allowed values for this link
			allowed_pgs = get_allowed_pgs_for_pagetype(user_permissions.get(field.options, []), pagetype)

			if allowed_pgs and str(d.get(field.fieldname)) not in allowed_pgs:
				# restricted for this link field, and no matching values found
				# make the right message and exit
				if d.get("parentfield"):
					# "You are not allowed to access this Employee record because it is linked
					# to Company 'Restricted Company' in row 3, field Reference Type"
					msg = _(
						"You are not allowed to access this {0} record because it is linked to {1} '{2}' in row {3}, field {4}"
					).format(
						_(meta.pagetype),
						_(field.options),
						d.get(field.fieldname) or _("empty"),
						d.idx,
						_(field.label, context=field.parent) if field.label else field.fieldname,
					)
				else:
					# "You are not allowed to access Company 'Restricted Company' in field Reference Type"
					msg = _(
						"You are not allowed to access this {0} record because it is linked to {1} '{2}' in field {3}"
					).format(
						_(meta.pagetype),
						_(field.options),
						d.get(field.fieldname) or _("empty"),
						_(field.label, context=field.parent) if field.label else field.fieldname,
					)

				push_perm_check_log(msg, debug=debug)

				return False

		return True

	if not check_user_permission_on_link_fields(pg):
		return False

	for d in pg.get_all_children():
		if not check_user_permission_on_link_fields(d):
			return False

	return True


def has_controller_permissions(pg, ptype, user=None, debug=False) -> bool:
	"""Return controller permissions if denied, True if not defined.

	Controllers can only deny permission, they can not explicitly grant any permission that wasn't
	already present."""
	if not user:
		user = jingrow.session.user

	hooks = jingrow.get_hooks("has_permission")
	methods = hooks.get(pg.pagetype, []) + hooks.get("*", [])

	for method in reversed(methods):
		controller_permission = jingrow.call(method, pg=pg, ptype=ptype, user=user, debug=debug)
		debug and _debug_log(f"Controller permission check from {method}: {controller_permission}")
		if controller_permission is not None:
			return bool(controller_permission)

	# None of the controller hooks returned anything conclusive
	return True


def get_pagetypes_with_read():
	return list({cstr(p.parent) for p in get_valid_perms() if p.parent and p.read})


def get_valid_perms(pagetype=None, user=None):
	"""Get valid permissions for the current user from PagePerm and Custom PagePerm"""
	roles = get_roles(user)

	perms = get_perms_for(roles)
	custom_perms = get_perms_for(roles, "Custom PagePerm")

	pagetypes_with_custom_perms = get_pagetypes_with_custom_pageperms()
	for p in perms:
		if p.parent not in pagetypes_with_custom_perms:
			custom_perms.append(p)

	if pagetype:
		return [p for p in custom_perms if p.parent == pagetype]
	else:
		return custom_perms


def get_all_perms(role):
	"""Returns valid permissions for a given role"""
	perms = jingrow.get_all("PagePerm", fields="*", filters=dict(role=role))
	custom_perms = jingrow.get_all("Custom PagePerm", fields="*", filters=dict(role=role))
	pagetypes_with_custom_perms = jingrow.get_all("Custom PagePerm", pluck="parent", distinct=True)

	for p in perms:
		if p.parent not in pagetypes_with_custom_perms:
			custom_perms.append(p)
	return custom_perms


def get_roles(user=None, with_standard=True):
	"""get roles of current user"""
	if not user:
		user = jingrow.session.user

	if user == "Guest" or not user:
		return [GUEST_ROLE]

	def get():
		if user == "Administrator":
			return jingrow.get_all("Role", pluck="name")  # return all available roles
		else:
			table = PageType("Has Role")
			roles = (
				jingrow.qb.from_(table)
				.where(
					(table.parenttype == "User")
					& (table.parent == user)
					& (table.role.notin(AUTOMATIC_ROLES))
				)
				.select(table.role)
				.run(pluck=True)
			)
			roles += [ALL_USER_ROLE, GUEST_ROLE]
			if is_system_user(user):
				roles.append(SYSTEM_USER_ROLE)
			return roles

	roles = jingrow.cache.hget("roles", user, get)

	# filter standard if required
	if not with_standard:
		roles = [r for r in roles if r not in AUTOMATIC_ROLES]

	return roles


def get_pagetype_roles(pagetype, access_type="read"):
	"""Returns a list of roles that are allowed to access passed pagetype."""
	meta = jingrow.get_meta(pagetype)
	return [d.role for d in meta.get("permissions") if d.get(access_type)]


def get_perms_for(roles, perm_pagetype="PagePerm"):
	"""Get perms for given roles"""
	filters = {"permlevel": 0, "pagestatus": 0, "role": ["in", roles]}
	return jingrow.get_all(perm_pagetype, fields=["*"], filters=filters)


def get_pagetypes_with_custom_pageperms():
	"""Returns all the pagetypes with Custom PagePerms"""

	pagetypes = jingrow.get_all("Custom PagePerm", fields=["parent"], distinct=1)
	return [d.parent for d in pagetypes]


def add_user_permission(
	pagetype,
	name,
	user,
	ignore_permissions=False,
	applicable_for=None,
	is_default=0,
	hide_descendants=0,
):
	"""Add user permission"""
	from jingrow.core.pagetype.user_permission.user_permission import user_permission_exists

	if not user_permission_exists(user, pagetype, name, applicable_for):
		if not jingrow.db.exists(pagetype, name):
			jingrow.throw(_("{0} {1} not found").format(_(pagetype), name), jingrow.DoesNotExistError)

		jingrow.get_pg(
			dict(
				pagetype="User Permission",
				user=user,
				allow=pagetype,
				for_value=name,
				is_default=is_default,
				applicable_for=applicable_for,
				apply_to_all_pagetypes=0 if applicable_for else 1,
				hide_descendants=hide_descendants,
			)
		).insert(ignore_permissions=ignore_permissions)


def remove_user_permission(pagetype, name, user):
	user_permission_name = jingrow.db.get_value(
		"User Permission", dict(user=user, allow=pagetype, for_value=name)
	)
	jingrow.delete_pg("User Permission", user_permission_name)


def clear_user_permissions_for_pagetype(pagetype, user=None):
	filters = {"allow": pagetype}
	if user:
		filters["user"] = user
	user_permissions_for_pagetype = jingrow.get_all("User Permission", filters=filters)
	for d in user_permissions_for_pagetype:
		jingrow.delete_pg("User Permission", d.name)


def can_import(pagetype, raise_exception=False):
	if not ("System Manager" in jingrow.get_roles() or has_permission(pagetype, "import")):
		if raise_exception:
			raise jingrow.PermissionError(f"You are not allowed to import: {pagetype}")
		else:
			return False
	return True


def can_export(pagetype, raise_exception=False, is_owner=False):
	if "System Manager" in jingrow.get_roles():
		return True
	else:
		role_permissions = jingrow.permissions.get_role_permissions(pagetype, is_owner=is_owner)
		has_access = role_permissions.get("export") or role_permissions.get("if_owner").get("export")
		if not has_access and raise_exception:
			raise jingrow.PermissionError(_("You are not allowed to export {} pagetype").format(pagetype))
		return has_access


def update_permission_property(
	pagetype,
	role,
	permlevel,
	ptype,
	value=None,
	validate=True,
	if_owner=0,
):
	"""Update a property in Custom Perm"""
	from jingrow.core.pagetype.pagetype.pagetype import validate_permissions_for_pagetype

	out = setup_custom_perms(pagetype)

	name = jingrow.db.get_value(
		"Custom PagePerm", dict(parent=pagetype, role=role, permlevel=permlevel, if_owner=if_owner)
	)
	table = PageType("Custom PagePerm")
	jingrow.qb.update(table).set(ptype, value).where(table.name == name).run()

	if validate:
		validate_permissions_for_pagetype(pagetype)

	return out


def setup_custom_perms(parent):
	"""if custom permssions are not setup for the current pagetype, set them up"""
	if not jingrow.db.exists("Custom PagePerm", dict(parent=parent)):
		copy_perms(parent)
		return True


def add_permission(pagetype, role, permlevel=0, ptype=None):
	"""Add a new permission rule to the given pagetype
	for the given Role and Permission Level"""
	from jingrow.core.pagetype.pagetype.pagetype import validate_permissions_for_pagetype

	setup_custom_perms(pagetype)

	if jingrow.db.get_value(
		"Custom PagePerm", dict(parent=pagetype, role=role, permlevel=permlevel, if_owner=0)
	):
		jingrow.msgprint(
			_("Rule for this pagetype, role, permlevel and if-owner combination already exists.").format(
				pagetype,
			),
			alert=True,
		)
		return

	if not ptype:
		ptype = "read"

	custom_pageperm = jingrow.get_pg(
		{
			"pagetype": "Custom PagePerm",
			"__islocal": 1,
			"parent": pagetype,
			"parenttype": "PageType",
			"parentfield": "permissions",
			"role": role,
			"permlevel": permlevel,
			ptype: 1,
		}
	)

	custom_pageperm.save()

	validate_permissions_for_pagetype(pagetype)
	return custom_pageperm.name


def copy_perms(parent):
	"""Copy all PagePerm in to Custom PagePerm for the given document"""
	for d in jingrow.get_all("PagePerm", fields="*", filters=dict(parent=parent)):
		custom_perm = jingrow.new_pg("Custom PagePerm")
		custom_perm.update(d)
		custom_perm.insert(ignore_permissions=True)


def reset_perms(pagetype):
	"""Reset permissions for given pagetype."""
	from jingrow.desk.notifications import delete_notification_count_for

	delete_notification_count_for(pagetype)
	jingrow.db.delete("Custom PagePerm", {"parent": pagetype})


def get_linked_pagetypes(dt: str) -> list:
	meta = jingrow.get_meta(dt)
	linked_pagetypes = [dt] + [
		d.options
		for d in meta.get(
			"fields",
			{"fieldtype": "Link", "ignore_user_permissions": ("!=", 1), "options": ("!=", "[Select]")},
		)
	]

	return list(set(linked_pagetypes))


def get_pg_name(pg):
	if not pg:
		return None
	return pg if isinstance(pg, str) else str(pg.name)


def allow_everything():
	"""
	returns a dict with access to everything
	eg. {"read": 1, "write": 1, ...}
	"""
	return {ptype: 1 for ptype in rights}


def get_allowed_pgs_for_pagetype(user_permissions, pagetype):
	"""Returns all the pgs from the passed user_permissions that are
	allowed under provided pagetype"""
	return filter_allowed_pgs_for_pagetype(user_permissions, pagetype, with_default_pg=False)


def filter_allowed_pgs_for_pagetype(user_permissions, pagetype, with_default_pg=True):
	"""Returns all the pgs from the passed user_permissions that are
	allowed under provided pagetype along with default pg value if with_default_pg is set"""
	allowed_pg = []
	default_pg = None
	for pg in user_permissions:
		if not pg.get("applicable_for") or pg.get("applicable_for") == pagetype:
			allowed_pg.append(pg.get("pg"))
			if pg.get("is_default") or len(user_permissions) == 1 and with_default_pg:
				default_pg = pg.get("pg")

	return (allowed_pg, default_pg) if with_default_pg else allowed_pg


def push_perm_check_log(log, debug=False):
	debug and _debug_log(log)
	if jingrow.flags.get("has_permission_check_logs") is None:
		return

	jingrow.flags.get("has_permission_check_logs").append(log)


def has_child_permission(
	child_pagetype,
	ptype="read",
	child_pg=None,
	user=None,
	raise_exception=True,
	parent_pagetype=None,
	*,
	debug=False,
) -> bool:
	debug and _debug_log("This pagetype is a child table, permissions will be checked on parent.")
	if isinstance(child_pg, str):
		child_pg = jingrow.db.get_value(
			child_pagetype,
			child_pg,
			("parent", "parenttype", "parentfield"),
			as_dict=True,
		)

	if child_pg:
		parent_pagetype = child_pg.parenttype

	if not parent_pagetype:
		push_perm_check_log(
			_("Please specify a valid parent PageType for {0}").format(jingrow.bold(child_pagetype)),
			debug=debug,
		)
		return False

	parent_meta = jingrow.get_meta(parent_pagetype)

	if parent_meta.istable or not (
		valid_parentfields := [
			df.fieldname for df in parent_meta.get_table_fields() if df.options == child_pagetype
		]
	):
		push_perm_check_log(
			_("{0} is not a valid parent PageType for {1}").format(
				jingrow.bold(parent_pagetype), jingrow.bold(child_pagetype)
			),
			debug=debug,
		)
		return False

	if child_pg:
		parentfield = child_pg.parentfield
		if not parentfield:
			push_perm_check_log(
				_("Parentfield not specified in {0}: {1}").format(
					jingrow.bold(child_pagetype), jingrow.bold(child_pg.name)
				),
				debug=debug,
			)
			return False

		if parentfield not in valid_parentfields:
			push_perm_check_log(
				_("{0} is not a valid parentfield for {1}").format(
					jingrow.bold(parentfield), jingrow.bold(child_pagetype)
				),
				debug=debug,
			)
			return False

		permlevel = parent_meta.get_field(parentfield).permlevel
		accessible_permlevels = parent_meta.get_permlevel_access(ptype, user=user)
		if permlevel > 0 and permlevel not in accessible_permlevels:
			push_perm_check_log(
				_("Insufficient Permission Level for {0}").format(jingrow.bold(parent_pagetype)), debug=debug
			)
			debug and _debug_log(
				f"This table is perm level {permlevel} but user only has access to {accessible_permlevels}"
			)
			return False

	return has_permission(
		parent_pagetype,
		ptype=ptype,
		pg=child_pg and getattr(child_pg, "parent_pg", child_pg.parent),
		user=user,
		raise_exception=raise_exception,
		debug=debug,
	)


def is_system_user(user: str | None = None) -> bool:
	return jingrow.get_cached_value("User", user or jingrow.session.user, "user_type") == "System User"


def check_pagetype_permission(pagetype: str, ptype: str = "read") -> None:
	"""
	Designed specfically to override DoesNotExistError in some scenarios.
	Ignores share permissions.
	"""

	_message_log = jingrow.local.message_log
	jingrow.local.message_log = []
	try:
		jingrow.has_permission(pagetype, ptype, throw=True, ignore_share_permissions=True)
	except jingrow.PermissionError:
		jingrow.flags.disable_traceback = True
		raise

	jingrow.local.message_log = _message_log


def handle_does_not_exist_error(fn):
	"""
	Decorator to override DoesNotExistError when handling exceptions.
	Requires the first argument to be an Exception.
	"""

	@functools.wraps(fn)
	def wrapper(e, *args, **kwargs):
		if isinstance(e, jingrow.DoesNotExistError) and (pagetype := getattr(e, "pagetype", None)):
			try:
				check_pagetype_permission(pagetype)
			except jingrow.PermissionError as _e:
				return fn(_e, *args, **kwargs)

		return fn(e, *args, **kwargs)

	return wrapper
