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

import jingrow
from jingrow import _
from jingrow.desk.form.utils import get_pdf_link
from jingrow.desk.notifications import clear_pagetype_notifications
from jingrow.email.pagetype.email_template.email_template import get_email_template
from jingrow.model.page import Page
from jingrow.model.workflow import (
	apply_workflow,
	get_workflow_name,
	get_workflow_state_field,
	has_approval_access,
	is_transition_condition_satisfied,
	send_email_alert,
)
from jingrow.query_builder import PageType
from jingrow.utils import get_datetime, get_url
from jingrow.utils.background_jobs import enqueue
from jingrow.utils.data import get_link_to_form
from jingrow.utils.user import get_users_with_role
from jingrow.utils.verified_command import get_signed_params, verify_request


class WorkflowAction(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 jingrow.workflow.pagetype.workflow_action_permitted_role.workflow_action_permitted_role import (
			WorkflowActionPermittedRole,
		)

		completed_by: DF.Link | None
		completed_by_role: DF.Link | None
		permitted_roles: DF.TableMultiSelect[WorkflowActionPermittedRole]
		reference_pagetype: DF.Link | None
		reference_name: DF.DynamicLink | None
		status: DF.Literal["Open", "Completed"]
		user: DF.Link | None
		workflow_state: DF.Data | None
	# end: auto-generated types
	pass


def on_pagetype_update():
	# The search order in any use case is no ["reference_name", "reference_pagetype", "status"]
	# The index scan would happen from left to right
	# so even if status is not in the where clause the index will be used
	jingrow.db.add_index("Workflow Action", ["reference_name", "reference_pagetype", "status"])


def get_permission_query_conditions(user):
	if not user:
		user = jingrow.session.user

	if user == "Administrator":
		return ""

	roles = jingrow.get_roles(user)

	WorkflowAction = PageType("Workflow Action")
	WorkflowActionPermittedRole = PageType("Workflow Action Permitted Role")

	permitted_workflow_actions = (
		jingrow.qb.from_(WorkflowAction)
		.join(WorkflowActionPermittedRole)
		.on(WorkflowAction.name == WorkflowActionPermittedRole.parent)
		.select(WorkflowAction.name)
		.where(WorkflowActionPermittedRole.role.isin(roles))
	).get_sql()

	return f"""(`tabWorkflow Action`.`name` in ({permitted_workflow_actions})
		or `tabWorkflow Action`.`user`={jingrow.db.escape(user)})
		and `tabWorkflow Action`.`status`='Open'
	"""


def has_permission(pg, user):
	if user == "Administrator":
		return True

	permitted_roles = {permitted_role.role for permitted_role in pg.permitted_roles}
	return not permitted_roles.isdisjoint(jingrow.get_roles(user))


def process_workflow_actions(pg, state):
	workflow = get_workflow_name(pg.get("pagetype"))
	if not workflow:
		return

	if state == "on_trash":
		clear_workflow_actions(pg.get("pagetype"), pg.get("name"))
		return

	if is_workflow_action_already_created(pg):
		return

	update_completed_workflow_actions(pg, workflow=workflow, workflow_state=get_pg_workflow_state(pg))
	clear_pagetype_notifications("Workflow Action")

	next_possible_transitions = get_next_possible_transitions(workflow, get_pg_workflow_state(pg), pg)

	if not next_possible_transitions:
		return

	roles = {t.allowed for t in next_possible_transitions}
	create_workflow_actions_for_roles(roles, pg)

	if send_email_alert(workflow):
		enqueue(
			send_workflow_action_email,
			queue="short",
			pg=pg,
			transitions=next_possible_transitions,
			enqueue_after_commit=True,
			now=jingrow.flags.in_test,
		)


@jingrow.whitelist(allow_guest=True)
def apply_action(action, pagetype, pagename, current_state, user=None, last_modified=None):
	if not verify_request():
		return

	pg = jingrow.get_pg(pagetype, pagename)
	pg_workflow_state = get_pg_workflow_state(pg)

	if pg_workflow_state == current_state:
		action_link = get_confirm_workflow_action_url(pg, action, user)

		if not last_modified or get_datetime(pg.modified) == get_datetime(last_modified):
			return_action_confirmation_page(pg, action, action_link)
		else:
			return_action_confirmation_page(pg, action, action_link, alert_pg_change=True)

	else:
		return_link_expired_page(pg, pg_workflow_state)


@jingrow.whitelist(allow_guest=True)
def confirm_action(pagetype, pagename, user, action):
	if not verify_request():
		return

	logged_in_user = jingrow.session.user
	if logged_in_user == "Guest" and user:
		# to allow user to apply action without login
		jingrow.set_user(user)

	pg = jingrow.get_pg(pagetype, pagename)
	newdoc = apply_workflow(pg, action)
	jingrow.db.commit()
	return_success_page(newdoc)

	# reset session user
	if logged_in_user == "Guest":
		jingrow.set_user(logged_in_user)


def return_success_page(pg):
	jingrow.respond_as_web_page(
		_("Success"),
		_("{0}: {1} is set to state {2}").format(
			pg.get("pagetype"), jingrow.bold(pg.get("name")), jingrow.bold(get_pg_workflow_state(pg))
		),
		indicator_color="green",
	)


def return_action_confirmation_page(pg, action, action_link, alert_pg_change=False):
	template_params = {
		"title": pg.get("name"),
		"pagetype": pg.get("pagetype"),
		"pagename": pg.get("name"),
		"action": action,
		"action_link": action_link,
		"alert_pg_change": alert_pg_change,
	}

	template_params["pdf_link"] = get_pdf_link(pg.get("pagetype"), pg.get("name"))

	jingrow.respond_as_web_page(
		title=None,
		html=None,
		indicator_color="blue",
		template="confirm_workflow_action",
		context=template_params,
	)


def return_link_expired_page(pg, pg_workflow_state):
	jingrow.respond_as_web_page(
		_("Link Expired"),
		_("Page {0} has been set to state {1} by {2}").format(
			jingrow.bold(pg.get("name")),
			jingrow.bold(pg_workflow_state),
			jingrow.bold(jingrow.get_value("User", pg.get("modified_by"), "full_name")),
		),
		indicator_color="blue",
	)


def update_completed_workflow_actions(pg, user=None, workflow=None, workflow_state=None):
	allowed_roles = get_allowed_roles(user, workflow, workflow_state)
	# There is no transaction leading upto this state
	# so no older actions to complete
	if not allowed_roles:
		return
	if workflow_action := get_workflow_action_by_role(pg, allowed_roles):
		update_completed_workflow_actions_using_role(user, workflow_action)
	else:
		# backwards compatibility
		# for workflow actions saved using user
		clear_old_workflow_actions_using_user(pg, user)
		update_completed_workflow_actions_using_user(pg, user)


def get_allowed_roles(user, workflow, workflow_state):
	user = user if user else jingrow.session.user

	allowed_roles = jingrow.get_all(
		"Workflow Transition",
		fields="allowed",
		filters=[["parent", "=", workflow], ["next_state", "=", workflow_state]],
		pluck="allowed",
	)

	user_roles = set(jingrow.get_roles(user))
	return set(allowed_roles).intersection(user_roles)


def get_workflow_action_by_role(pg, allowed_roles):
	WorkflowAction = PageType("Workflow Action")
	WorkflowActionPermittedRole = PageType("Workflow Action Permitted Role")
	return (
		jingrow.qb.from_(WorkflowAction)
		.join(WorkflowActionPermittedRole)
		.on(WorkflowAction.name == WorkflowActionPermittedRole.parent)
		.select(WorkflowAction.name, WorkflowActionPermittedRole.role)
		.where(
			(WorkflowAction.reference_name == pg.get("name"))
			& (WorkflowAction.reference_pagetype == pg.get("pagetype"))
			& (WorkflowAction.status == "Open")
			& (WorkflowActionPermittedRole.role.isin(list(allowed_roles)))
		)
		.orderby(WorkflowActionPermittedRole.role)
		.limit(1)
	).run(as_dict=True)


def update_completed_workflow_actions_using_role(user=None, workflow_action=None):
	user = user if user else jingrow.session.user
	WorkflowAction = PageType("Workflow Action")

	if not workflow_action:
		return

	(
		jingrow.qb.update(WorkflowAction)
		.set(WorkflowAction.status, "Completed")
		.set(WorkflowAction.completed_by, user)
		.set(WorkflowAction.completed_by_role, workflow_action[0].role)
		.where(WorkflowAction.name == workflow_action[0].name)
	).run()


def clear_old_workflow_actions_using_user(pg, user=None):
	user = user if user else jingrow.session.user

	if jingrow.db.has_column("Workflow Action", "user"):
		jingrow.db.delete(
			"Workflow Action",
			{
				"reference_name": pg.get("name"),
				"reference_pagetype": pg.get("pagetype"),
				"status": "Open",
				"user": ("!=", user),
			},
		)


def update_completed_workflow_actions_using_user(pg, user=None):
	user = user or jingrow.session.user

	if jingrow.db.has_column("Workflow Action", "user"):
		WorkflowAction = PageType("Workflow Action")
		(
			jingrow.qb.update(WorkflowAction)
			.set(WorkflowAction.status, "Completed")
			.set(WorkflowAction.completed_by, user)
			.where(
				(WorkflowAction.reference_name == pg.get("name"))
				& (WorkflowAction.reference_pagetype == pg.get("pagetype"))
				& (WorkflowAction.status == "Open")
				& (WorkflowAction.user == user)
			)
		).run()


def get_next_possible_transitions(workflow_name, state, pg=None):
	transitions = jingrow.get_all(
		"Workflow Transition",
		fields=["allowed", "action", "state", "allow_self_approval", "next_state", "condition"],
		filters=[["parent", "=", workflow_name], ["state", "=", state]],
	)

	transitions_to_return = []

	for transition in transitions:
		is_next_state_optional = get_state_optional_field_value(workflow_name, transition.next_state)
		# skip transition if next state of the transition is optional
		if is_next_state_optional:
			continue
		if not is_transition_condition_satisfied(transition, pg):
			continue
		transitions_to_return.append(transition)

	return transitions_to_return


def get_users_next_action_data(transitions, pg):
	user_data_map = {}

	@jingrow.request_cache
	def user_has_permission(user: str) -> bool:
		from jingrow.permissions import has_permission

		return has_permission(pagetype=pg, user=user)

	for transition in transitions:
		users = get_users_with_role(transition.allowed)
		filtered_users = [
			user for user in users if has_approval_access(user, pg, transition) and user_has_permission(user)
		]
		if pg.get("owner") in filtered_users and not transition.get("send_email_to_creator"):
			filtered_users.remove(pg.get("owner"))
		for user in filtered_users:
			if not user_data_map.get(user):
				user_data_map[user] = jingrow._dict(
					{
						"possible_actions": [],
						"email": jingrow.db.get_value("User", user, "email"),
					}
				)

			user_data_map[user].get("possible_actions").append(
				jingrow._dict(
					{
						"action_name": transition.action,
						"action_link": get_workflow_action_url(transition.action, pg, user),
					}
				)
			)
	return user_data_map


def create_workflow_actions_for_roles(roles, pg):
	if not roles:
		return
	workflow_action = jingrow.get_pg(
		{
			"pagetype": "Workflow Action",
			"reference_pagetype": pg.get("pagetype"),
			"reference_name": pg.get("name"),
			"workflow_state": get_pg_workflow_state(pg),
			"status": "Open",
		}
	)

	for role in roles:
		workflow_action.append("permitted_roles", {"role": role})

	workflow_action.insert(ignore_permissions=True)


def send_workflow_action_email(pg, transitions):
	users_data = get_users_next_action_data(transitions, pg)
	common_args = get_common_email_args(pg)
	message = common_args.pop("message", None)
	for data in users_data.values():
		email_args = {
			"recipients": [data.get("email")],
			"args": {"actions": list(deduplicate_actions(data.get("possible_actions"))), "message": message},
			"reference_name": pg.name,
			"reference_pagetype": pg.pagetype,
		}
		email_args.update(common_args)
		try:
			jingrow.sendmail(**email_args)
		except jingrow.OutgoingEmailError:
			# Emails config broken, don't bother retrying next user.
			jingrow.log_error("Failed to send workflow action email")
			return


def deduplicate_actions(action_list):
	action_map = {}
	for action_data in action_list:
		if not action_map.get(action_data.action_name):
			action_map[action_data.action_name] = action_data

	return action_map.values()


def get_workflow_action_url(action, pg, user):
	apply_action_method = "/api/action/jingrow.workflow.pagetype.workflow_action.workflow_action.apply_action"

	params = {
		"pagetype": pg.get("pagetype"),
		"pagename": pg.get("name"),
		"action": action,
		"current_state": get_pg_workflow_state(pg),
		"user": user,
		"last_modified": pg.get("modified"),
	}

	return get_url(apply_action_method + "?" + get_signed_params(params))


def get_confirm_workflow_action_url(pg, action, user):
	confirm_action_method = (
		"/api/action/jingrow.workflow.pagetype.workflow_action.workflow_action.confirm_action"
	)

	params = {
		"action": action,
		"pagetype": pg.get("pagetype"),
		"pagename": pg.get("name"),
		"user": user,
	}

	return get_url(confirm_action_method + "?" + get_signed_params(params))


def is_workflow_action_already_created(pg):
	return jingrow.db.exists(
		{
			"pagetype": "Workflow Action",
			"reference_name": pg.get("name"),
			"reference_pagetype": pg.get("pagetype"),
			"workflow_state": get_pg_workflow_state(pg),
		}
	)


def clear_workflow_actions(pagetype, name):
	if not (pagetype and name):
		return
	jingrow.db.delete(
		"Workflow Action",
		filters={
			"reference_name": name,
			"reference_pagetype": pagetype,
		},
	)


def get_pg_workflow_state(pg):
	workflow_name = get_workflow_name(pg.get("pagetype"))
	workflow_state_field = get_workflow_state_field(workflow_name)
	return pg.get(workflow_state_field)


def get_common_email_args(pg):
	pagetype = pg.get("pagetype")
	pagename = pg.get("name")

	email_template = get_email_template_from_workflow(pg)
	if email_template:
		subject = email_template.get("subject")
		response = email_template.get("message")
	else:
		subject = _("Workflow Action") + f" on {pagetype}: {pagename}"
		response = get_link_to_form(pagetype, pagename, f"{pagetype}: {pagename}")

	print_format = pg.meta.default_print_format
	lang = pg.get("language") or (
		jingrow.get_cached_value("Print Format", print_format, "default_print_language")
		if print_format
		else None
	)

	return {
		"template": "workflow_action",
		"header": "Workflow Action",
		"attachments": [
			jingrow.attach_print(
				pagetype,
				pagename,
				file_name=pagename,
				pg=pg,
				lang=lang,
				print_format=print_format,
			)
		],
		"subject": subject,
		"message": response,
	}


def get_email_template_from_workflow(pg):
	"""Return next_action_email_template for workflow state (if available) based on pg current workflow state."""
	workflow_name = get_workflow_name(pg.get("pagetype"))
	pg_state = get_pg_workflow_state(pg)
	template_name = jingrow.db.get_value(
		"Workflow Page State",
		{"parent": workflow_name, "state": pg_state},
		"next_action_email_template",
	)

	if not template_name:
		return

	if isinstance(pg, Page):
		pg = pg.as_dict()
	return get_email_template(template_name, pg)


def get_state_optional_field_value(workflow_name, state):
	return jingrow.get_cached_value(
		"Workflow Page State", {"parent": workflow_name, "state": state}, "is_optional_state"
	)
