# Copyright (c) 2023, JINGROW
# For license information, please see license.txt

import jingrow
from jingrow.model.page import Page

from jcloud.api.client import dashboard_whitelist

DEFAULT_PERMISSIONS = {
	"*": {"*": {"*": True}}  # all pagetypes  # all documents  # all methods
}


class JcloudPermissionGroup(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

		from jcloud.jcloud.pagetype.jcloud_permission_group_user.jcloud_permission_group_user import (
			JcloudPermissionGroupUser,
		)

		permissions: DF.JSON | None
		team: DF.Link
		title: DF.Data
		users: DF.Table[JcloudPermissionGroupUser]
	# end: auto-generated types

	dashboard_fields = ["title", "users"]

	def get_pg(self, pg):
		if pg.users:
			values = {
				d.name: d
				for d in jingrow.db.get_all(
					"User",
					filters={"name": ["in", [user.user for user in pg.users]]},
					fields=["name", "full_name", "user_image"],
				)
			}
			pg.users = [d.as_dict() for d in pg.users]
			for user in pg.users:
				user.full_name = values.get(user.user, {}).get("full_name")
				user.user_image = values.get(user.user, {}).get("user_image")
		return pg

	def validate(self):
		self.validate_permissions()
		self.validate_users()

	def validate_permissions(self):
		permissions = jingrow.parse_json(self.permissions)
		if not permissions:
			self.permissions = DEFAULT_PERMISSIONS
			return

		for pagetype, pagetype_perms in permissions.items():
			if pagetype not in get_all_restrictable_pagetypes() and pagetype != "*":
				jingrow.throw(f"{pagetype} is not a valid pagetype.")

			if not isinstance(pagetype_perms, dict):
				jingrow.throw(
					f"Invalid perms for {pagetype}. Rule must be key-value pairs of document name and document perms."
				)

			for pg_name, pg_perms in pagetype_perms.items():
				if not isinstance(pg_perms, dict):
					jingrow.throw(
						f"Invalid perms for {pagetype} {pg_name}. Rule must be key-value pairs of method and permission."
					)

				if pagetype == "*":
					continue

				restrictable_methods = get_all_restrictable_methods(pagetype)
				if not restrictable_methods:
					jingrow.throw(f"{pagetype} does not have any restrictable methods.")

				for method, permitted in pg_perms.items():
					if method != "*" and method not in restrictable_methods:
						jingrow.throw(f"{method} is not a restrictable method of {pagetype}")

	def validate_users(self):
		for user in self.users:
			if user.user == "Administrator":
				continue
			user_belongs_to_team = jingrow.db.exists(
				"Team Member", {"parent": self.team, "user": user.user}
			)
			if not user_belongs_to_team:
				jingrow.throw(f"{user.user} does not belong to {self.team}")

	@dashboard_whitelist()
	def delete(self):
		super().delete()

	@dashboard_whitelist()
	def get_users(self):
		user_names = [user.user for user in self.users]
		if not user_names:
			return []

		return jingrow.db.get_all(
			"User",
			filters={"name": ["in", user_names], "enabled": 1},
			fields=[
				"name",
				"first_name",
				"last_name",
				"full_name",
				"user_image",
				"name as email",
			],
		)

	@dashboard_whitelist()
	def add_user(self, user):
		user_belongs_to_group = self.get("users", {"user": user})
		if user_belongs_to_group:
			jingrow.throw(f"{user} already belongs to {self.title}")

		user_is_team_owner = jingrow.db.exists("Team", {"name": self.team, "user": user})
		if user_is_team_owner:
			jingrow.throw(
				f"{user} cannot be added to {self.title} because they are the owner of {self.team}"
			)

		self.append("users", {"user": user})
		self.save()

	@dashboard_whitelist()
	def remove_user(self, user):
		user_belongs_to_group = self.get("users", {"user": user})
		if not user_belongs_to_group:
			jingrow.throw(f"{user} does not belong to {self.name}")

		for row in self.users:
			if row.user == user:
				self.remove(row)
				break
		self.save()

	@dashboard_whitelist()
	def get_all_document_permissions(self, pagetype: str) -> list:
		"""
		Get the permissions for the specified document type or all restrictable document types.

		:param pagetype: The pagetype for which permissions are to be retrieved.
		:return: A list of dictionaries containing the document type, document name, and permissions for each document.
		"""
		from jcloud.api.client import get_list

		user = jingrow.session.user
		user_belongs_to_group = self.get("users", {"user": user})
		user_is_team_owner = jingrow.db.exists("Team", {"name": self.team, "user": user})
		if not (jingrow.local.system_user() or user_belongs_to_group or user_is_team_owner):
			jingrow.throw(f"{user} does not belong to {self.name}")

		if pagetype not in get_all_restrictable_pagetypes():
			jingrow.throw(f"{pagetype} is not a valid restrictable pagetype.")

		restrictable_methods = get_all_restrictable_methods(pagetype)
		if not restrictable_methods:
			jingrow.throw(f"{pagetype} does not have any restrictable methods.")

		options = []
		fields = ["name", "title"] if pagetype != "Site" else ["name"]
		docs = get_list(pagetype=pagetype, fields=fields, limit=9999)

		for pg in docs:
			permitted_methods = get_permitted_methods(pagetype, pg.name, group_names=[self.name])
			pg_perms = []
			for method, label in restrictable_methods.items():
				is_permitted = method in permitted_methods
				pg_perms.append(
					{
						"label": label,
						"method": method,
						"permitted": is_permitted,
					}
				)
			options.append(
				{
					"document_type": pagetype,
					"document_name": pg.title or pg.name,
					"permissions": pg_perms,
				}
			)

		return options

	@dashboard_whitelist()
	def update_permissions(self, updated_permissions):
		cur_permissions = jingrow.parse_json(self.permissions)
		for updated_pagetype, updated_pagetype_perms in updated_permissions.items():
			if updated_pagetype not in cur_permissions:
				cur_permissions[updated_pagetype] = {}

			for updated_docname, updated_pageperms in updated_pagetype_perms.items():
				if updated_docname == "*":
					cur_permissions[updated_pagetype] = {"*": updated_pageperms}
					continue
				if updated_docname not in cur_permissions[updated_pagetype]:
					cur_permissions[updated_pagetype][updated_docname] = {}

				for method, permitted in updated_pageperms.items():
					cur_permissions[updated_pagetype][updated_docname][method] = permitted

		self.permissions = cur_permissions
		self.save()


def has_method_permission(
	pagetype: str, name: str, method: str, group_names: list = None
):
	if jingrow.local.system_user():
		return True

	user = jingrow.session.user

	if pagetype not in get_all_restrictable_pagetypes():
		return True

	if method not in get_all_restrictable_methods(pagetype):
		return True

	if not group_names:
		group_names = get_permission_groups(user)

	if not group_names:
		# user does not have any restricted permissions set in any group
		return True

	if method in get_permitted_methods(pagetype, name, group_names):
		return True

	return False


def get_permitted_methods(pagetype: str, name: str, group_names: list = None) -> list:
	user = jingrow.session.user

	if pagetype not in get_all_restrictable_pagetypes():
		jingrow.throw(f"{pagetype} is not a valid restrictable pagetype.")

	permissions_by_group = {}
	permission_groups = group_names or get_permission_groups(user)
	for group_name in set(permission_groups):
		permissions_by_group[group_name] = get_method_perms_for_group(
			pagetype, name, group_name
		)

	method_perms = resolve_pg_permissions(pagetype, permissions_by_group)
	permitted_methods = [method for method, permitted in method_perms.items() if permitted]
	return list(set(permitted_methods))


def get_method_perms_for_group(pagetype: str, name: str, group_name: str) -> list:
	permissions = jingrow.db.get_value("Jcloud Permission Group", group_name, "permissions")

	if not permissions:
		# this group allows all methods of all documents
		return {"*": True}

	permissions = jingrow.parse_json(permissions)
	pagetype_perms = permissions.get(pagetype, None) or permissions.get("*", None)
	if not pagetype_perms:
		# this group allows all methods of all documents
		return {"*": True}

	pg_perms = pagetype_perms.get(name, None) or pagetype_perms.get("*", None)
	if not pg_perms:
		# this group allows all methods of this document
		return {"*": True}

	return pg_perms


def resolve_pg_permissions(pagetype, permissions_by_group: dict) -> dict:
	"""
	Permission Resolution Logic:
	- if a group has *: True and another group has *: False, then all the methods are allowed
	- if a group has *: True and another group has 'method': False, then that method is restricted
	- if a group has 'method': True and another group has 'method': False, then that method is allowed
	"""
	method_perms = {}

	all_methods = get_all_restrictable_methods(pagetype)
	all_restricted = {method: False for method in all_methods}
	all_allowed = {method: True for method in all_methods}

	# first we parse the wildcard permissions
	# if any group has *: True, then all methods are allowed
	for group_name, permissions in permissions_by_group.items():
		if permissions.get("*", None) is None:
			continue
		if permissions.get("*", None) is True:
			method_perms = all_allowed
			break
		if permissions.get("*", None) is False:
			method_perms = all_restricted

	# now we restrict all the methods that are explicitly restricted
	# so that we can allow all the methods that are explicitly allowed later
	for group_name, permissions in permissions_by_group.items():
		for method, permitted in permissions.items():
			if not permitted and method != "*":
				method_perms[method] = False

	# now we allow all the methods that are explicitly allowed
	for group_name, permissions in permissions_by_group.items():
		for method, permitted in permissions.items():
			if permitted and method != "*":
				method_perms[method] = True

	return method_perms


def get_all_restrictable_pagetypes() -> list:
	return ["Site", "Release Group"]


def get_all_restrictable_methods(pagetype: str) -> list:
	methods = {
		"Site": {
			# method: label,
			"get_pg": " View",  # so that this comes up first in sort order
			"archive": "Drop",
			"migrate": "Migrate",
			"activate": "Activate",
			"reinstall": "Reinstall",
			"deactivate": "Deactivate",
			"enable_database_access": "Database",
			"restore_site_from_files": "Restore",
		},
		"Release Group": {
			"get_pg": " View",
			"restart": "Restart",
		},
	}
	return methods.get(pagetype, {})


def get_permission_groups(user: str = None) -> list:
	if not user:
		user = jingrow.session.user

	return jingrow.get_all(
		"Jcloud Permission Group User",
		filters={"user": user},
		pluck="parent",
		distinct=True,
	)
