# Copyright (c) 2021, JINGROW
# License: MIT. See LICENSE

import json

import jingrow
from jingrow import _
from jingrow.core.utils import find
from jingrow.desk.form.linked_with import get_linked_pagetypes
from jingrow.model.page import Page
from jingrow.utils import cstr


class UserPermission(Page):
	# begin: auto-generated types
	# This code is auto-generated. Do not modify anything in this block.

	from typing import TYPE_CHECKING

	if TYPE_CHECKING:
		from jingrow.types import DF

		allow: DF.Link
		applicable_for: DF.Link | None
		apply_to_all_pagetypes: DF.Check
		for_value: DF.DynamicLink
		hide_descendants: DF.Check
		is_default: DF.Check
		user: DF.Link

	# end: auto-generated types
	def validate(self):
		self.validate_user_permission()
		self.validate_default_permission()

	def on_update(self):
		jingrow.cache.hdel("user_permissions", self.user)
		jingrow.publish_realtime("update_user_permissions", user=self.user, after_commit=True)

	def on_trash(self):
		jingrow.cache.hdel("user_permissions", self.user)
		jingrow.publish_realtime("update_user_permissions", user=self.user, after_commit=True)

	def validate_user_permission(self):
		"""checks for duplicate user permission records"""

		duplicate_exists = jingrow.get_all(
			self.pagetype,
			filters={
				"allow": self.allow,
				"for_value": self.for_value,
				"user": self.user,
				"applicable_for": cstr(self.applicable_for),
				"apply_to_all_pagetypes": self.apply_to_all_pagetypes,
				"name": ["!=", self.name],
			},
			limit=1,
		)
		if duplicate_exists:
			jingrow.throw(_("User permission already exists"), jingrow.DuplicateEntryError)

	def validate_default_permission(self):
		"""validate user permission overlap for default value of a particular pagetype"""
		overlap_exists = []
		if self.is_default:
			overlap_exists = jingrow.get_all(
				self.pagetype,
				filters={"allow": self.allow, "user": self.user, "is_default": 1, "name": ["!=", self.name]},
				or_filters={
					"applicable_for": cstr(self.applicable_for),
					"apply_to_all_pagetypes": 1,
				},
				limit=1,
			)
		if overlap_exists:
			ref_link = jingrow.get_desk_link(self.pagetype, overlap_exists[0].name)
			jingrow.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow))


def send_user_permissions(bootinfo):
	bootinfo.user["user_permissions"] = get_user_permissions()


@jingrow.whitelist()
def get_user_permissions(user=None):
	"""Get all users permissions for the user as a dict of pagetype"""
	# if this is called from client-side,
	# user can access only his/her user permissions
	if jingrow.request and jingrow.local.form_dict.cmd == "get_user_permissions":
		user = jingrow.session.user

	if not user:
		user = jingrow.session.user

	if not user or user in ("Administrator", "Guest"):
		return {}

	cached_user_permissions = jingrow.cache.hget("user_permissions", user)

	if cached_user_permissions is not None:
		return cached_user_permissions

	out = {}

	def add_pg_to_perm(perm, pg_name, is_default):
		# group rules for each type
		# for example if allow is "Customer", then build all allowed customers
		# in a list
		if not out.get(perm.allow):
			out[perm.allow] = []

		out[perm.allow].append(
			jingrow._dict(
				{"pg": pg_name, "applicable_for": perm.get("applicable_for"), "is_default": is_default}
			)
		)

	try:
		for perm in jingrow.get_all(
			"User Permission",
			fields=["allow", "for_value", "applicable_for", "is_default", "hide_descendants"],
			filters=dict(user=user),
		):
			meta = jingrow.get_meta(perm.allow)
			add_pg_to_perm(perm, perm.for_value, perm.is_default)

			if meta.is_nested_set() and not perm.hide_descendants:
				decendants = jingrow.db.get_descendants(perm.allow, perm.for_value)
				for pg in decendants:
					add_pg_to_perm(perm, pg, False)

		out = jingrow._dict(out)
		jingrow.cache.hset("user_permissions", user, out)
	except jingrow.db.SQLError as e:
		if jingrow.db.is_table_missing(e):
			# called from patch
			pass

	return out


def user_permission_exists(user, allow, for_value, applicable_for=None):
	"""Checks if similar user permission already exists"""
	user_permissions = get_user_permissions(user).get(allow, [])
	if not user_permissions:
		return None
	return find(
		user_permissions,
		lambda perm: perm["pg"] == for_value and perm.get("applicable_for") == applicable_for,
	)


@jingrow.whitelist()
@jingrow.validate_and_sanitize_search_inputs
def get_applicable_for_pagetype_list(pagetype, txt, searchfield, start, page_len, filters):
	linked_pagetypes_map = get_linked_pagetypes(pagetype, True)

	linked_pagetypes = []
	for linked_pagetype, linked_pagetype_values in linked_pagetypes_map.items():
		linked_pagetypes.append(linked_pagetype)
		child_pagetype = linked_pagetype_values.get("child_pagetype")
		if child_pagetype:
			linked_pagetypes.append(child_pagetype)

	linked_pagetypes += [pagetype]

	if txt:
		linked_pagetypes = [d for d in linked_pagetypes if txt.lower() in d.lower()]

	linked_pagetypes.sort()

	return [[pagetype] for pagetype in linked_pagetypes[start:page_len]]


def get_permitted_documents(pagetype):
	"""Returns permitted documents from the given pagetype for the session user"""
	# sort permissions in a way to make the first permission in the list to be default
	user_perm_list = sorted(
		get_user_permissions().get(pagetype, []), key=lambda x: x.get("is_default"), reverse=True
	)

	return [d.get("pg") for d in user_perm_list if d.get("pg")]


@jingrow.whitelist()
def check_applicable_pg_perm(user, pagetype, pagename):
	jingrow.only_for("System Manager")
	applicable = []
	pg_exists = jingrow.get_all(
		"User Permission",
		fields=["name"],
		filters={
			"user": user,
			"allow": pagetype,
			"for_value": pagename,
			"apply_to_all_pagetypes": 1,
		},
		limit=1,
	)
	if pg_exists:
		applicable = get_linked_pagetypes(pagetype).keys()
	else:
		data = jingrow.get_all(
			"User Permission",
			fields=["applicable_for"],
			filters={
				"user": user,
				"allow": pagetype,
				"for_value": pagename,
			},
		)
		for permission in data:
			applicable.append(permission.applicable_for)
	return applicable


@jingrow.whitelist()
def clear_user_permissions(user, for_pagetype):
	jingrow.only_for("System Manager")
	total = jingrow.db.count("User Permission", {"user": user, "allow": for_pagetype})

	if total:
		jingrow.db.delete(
			"User Permission",
			{
				"allow": for_pagetype,
				"user": user,
			},
		)
		jingrow.clear_cache()

	return total


@jingrow.whitelist()
def add_user_permissions(data):
	"""Add and update the user permissions"""
	jingrow.only_for("System Manager")
	if isinstance(data, str):
		data = json.loads(data)
	data = jingrow._dict(data)

	# get all pagetypes on whom this permission is applied
	perm_applied_pgs = check_applicable_pg_perm(data.user, data.pagetype, data.pagename)
	exists = jingrow.db.exists(
		"User Permission",
		{
			"user": data.user,
			"allow": data.pagetype,
			"for_value": data.pagename,
			"apply_to_all_pagetypes": 1,
		},
	)
	if data.apply_to_all_pagetypes == 1 and not exists:
		remove_applicable(perm_applied_pgs, data.user, data.pagetype, data.pagename)
		insert_user_perm(
			data.user, data.pagetype, data.pagename, data.is_default, data.hide_descendants, apply_to_all=1
		)
		return 1
	elif len(data.applicable_pagetypes) > 0 and data.apply_to_all_pagetypes != 1:
		remove_apply_to_all(data.user, data.pagetype, data.pagename)
		update_applicable(perm_applied_pgs, data.applicable_pagetypes, data.user, data.pagetype, data.pagename)
		for applicable in data.applicable_pagetypes:
			if applicable not in perm_applied_pgs:
				insert_user_perm(
					data.user,
					data.pagetype,
					data.pagename,
					data.is_default,
					data.hide_descendants,
					applicable=applicable,
				)
			elif exists:
				insert_user_perm(
					data.user,
					data.pagetype,
					data.pagename,
					data.is_default,
					data.hide_descendants,
					applicable=applicable,
				)
		return 1
	return 0


def insert_user_perm(
	user, pagetype, pagename, is_default=0, hide_descendants=0, apply_to_all=None, applicable=None
):
	user_perm = jingrow.new_pg("User Permission")
	user_perm.user = user
	user_perm.allow = pagetype
	user_perm.for_value = pagename
	user_perm.is_default = is_default
	user_perm.hide_descendants = hide_descendants
	if applicable:
		user_perm.applicable_for = applicable
		user_perm.apply_to_all_pagetypes = 0
	else:
		user_perm.apply_to_all_pagetypes = 1
	user_perm.insert()


def remove_applicable(perm_applied_pgs, user, pagetype, pagename):
	for applicable_for in perm_applied_pgs:
		jingrow.db.delete(
			"User Permission",
			{
				"applicable_for": applicable_for,
				"for_value": pagename,
				"allow": pagetype,
				"user": user,
			},
		)


def remove_apply_to_all(user, pagetype, pagename):
	jingrow.db.delete(
		"User Permission",
		{
			"apply_to_all_pagetypes": 1,
			"for_value": pagename,
			"allow": pagetype,
			"user": user,
		},
	)


def update_applicable(already_applied, to_apply, user, pagetype, pagename):
	for applied in already_applied:
		if applied not in to_apply:
			jingrow.db.delete(
				"User Permission",
				{
					"applicable_for": applied,
					"for_value": pagename,
					"allow": pagetype,
					"user": user,
				},
			)
