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

import json
import typing
from urllib.parse import quote

import jingrow
import jingrow.defaults
import jingrow.desk.form.meta
import jingrow.utils
from jingrow import _, _dict
from jingrow.desk.form.page_follow import is_document_followed
from jingrow.model.utils.user_settings import get_user_settings
from jingrow.permissions import check_pagetype_permission, get_pg_permissions
from jingrow.utils.data import cstr

if typing.TYPE_CHECKING:
	from jingrow.model.page import Page


@jingrow.whitelist()
def getpage(pagetype, name):
	"""
	Loads a doclist for a given document. This method is called directly from the client.
	Requries "pagetype", "name" as form variables.
	Will also call the "onload" method on the document.
	"""

	if not (pagetype and name):
		raise Exception("pagetype and name required!")

	try:
		pg = jingrow.get_pg(pagetype, name)
	except jingrow.DoesNotExistError:
		check_pagetype_permission(pagetype)
		jingrow.clear_last_message()
		return []

	if not pg.has_permission("read"):
		check_pagetype_permission(pagetype)
		jingrow.flags.error_message = _("Insufficient Permission for {0}").format(
			jingrow.bold(_(pagetype) + " " + name)
		)
		raise jingrow.PermissionError(("read", pagetype, name))

	# Replace cache if stale one exists
	# PERF: This should be eventually removed completely when we are sure about caching correctness
	if (key := jingrow.can_cache_pg((pagetype, name))) and jingrow.cache.exists(key):
		jingrow._set_document_in_cache(key, pg)

	run_onload(pg)
	pg.apply_fieldlevel_read_permissions()

	# add file list
	pg.add_viewed()
	get_pageinfo(pg)

	pg.add_seen()
	set_link_titles(pg)
	if jingrow.response.pgs is None:
		jingrow.local.response = _dict({"pgs": []})
	jingrow.response.pgs.append(pg)


@jingrow.whitelist()
def getpagetype(pagetype, with_parent=False, cached_timestamp=None):
	"""load pagetype"""

	pgs = []
	parent_dt = None

	# with parent (called from report builder)
	if with_parent and (parent_dt := jingrow.model.meta.get_parent_dt(pagetype)):
		pgs = get_meta_bundle(parent_dt)
		jingrow.response["parent_dt"] = parent_dt

	if not pgs:
		pgs = get_meta_bundle(pagetype)

	jingrow.response["user_settings"] = get_user_settings(parent_dt or pagetype)

	if cached_timestamp and pgs[0].modified == cached_timestamp:
		return "use_cache"

	jingrow.response.pgs.extend(pgs)


def get_meta_bundle(pagetype):
	bundle = [jingrow.desk.form.meta.get_meta(pagetype)]
	bundle.extend(
		jingrow.desk.form.meta.get_meta(df.options)
		for df in bundle[0].fields
		if df.fieldtype in jingrow.model.table_fields
	)
	return bundle


@jingrow.whitelist()
def get_pageinfo(pg=None, pagetype=None, name=None):
	from jingrow.share import _get_users as get_pageshares

	if not pg:
		pg = jingrow.get_pg(pagetype, name)
		if not pg.has_permission("read"):
			raise jingrow.PermissionError

	all_communications = _get_communications(pg.pagetype, pg.name, limit=21)
	automated_messages = [
		msg for msg in all_communications if msg["communication_type"] == "Automated Message"
	]
	communications_except_auto_messages = [
		msg for msg in all_communications if msg["communication_type"] != "Automated Message"
	]

	pageinfo = jingrow._dict(user_info={})

	add_comments(pg, pageinfo)

	pageinfo.update(
		{
			"pagetype": pg.pagetype,
			"name": pg.name,
			"attachments": get_attachments(pg.pagetype, pg.name),
			"communications": communications_except_auto_messages,
			"automated_messages": automated_messages,
			"versions": get_versions(pg),
			"assignments": get_assignments(pg.pagetype, pg.name),
			"permissions": get_pg_permissions(pg),
			"shared": get_pageshares(pg),
			"views": get_view_logs(pg),
			"energy_point_logs": get_point_logs(pg.pagetype, pg.name),
			"additional_timeline_content": get_additional_timeline_content(pg.pagetype, pg.name),
			"milestones": get_milestones(pg.pagetype, pg.name),
			"is_document_followed": is_document_followed(pg.pagetype, pg.name, jingrow.session.user),
			"tags": get_tags(pg.pagetype, pg.name),
			"document_email": get_document_email(pg.pagetype, pg.name),
		}
	)

	update_user_info(pageinfo)

	jingrow.response["pageinfo"] = pageinfo


def add_comments(pg, pageinfo):
	# divide comments into separate lists
	pageinfo.comments = []
	pageinfo.shared = []
	pageinfo.assignment_logs = []
	pageinfo.attachment_logs = []
	pageinfo.info_logs = []
	pageinfo.like_logs = []
	pageinfo.workflow_logs = []

	comments = jingrow.get_all(
		"Comment",
		fields=["name", "creation", "content", "owner", "comment_type"],
		filters={"reference_pagetype": pg.pagetype, "reference_name": pg.name},
	)

	for c in comments:
		match c.comment_type:
			case "Comment":
				c.content = jingrow.utils.markdown(c.content)
				pageinfo.comments.append(c)
			case "Shared" | "Unshared":
				pageinfo.shared.append(c)
			case "Assignment Completed" | "Assigned":
				pageinfo.assignment_logs.append(c)
			case "Attachment" | "Attachment Removed":
				pageinfo.attachment_logs.append(c)
			case "Info" | "Edit" | "Label" | "Rename" | "Merge" | "Restore":
				pageinfo.info_logs.append(c)
			case "Like":
				pageinfo.like_logs.append(c)
			case "Workflow":
				pageinfo.workflow_logs.append(c)

	return comments


def get_milestones(pagetype, name):
	return jingrow.get_all(
		"Milestone",
		fields=["creation", "owner", "track_field", "value"],
		filters=dict(reference_type=pagetype, reference_name=name),
	)


def get_attachments(dt, dn):
	return jingrow.get_all(
		"File",
		fields=["name", "file_name", "file_url", "is_private"],
		filters={"attached_to_name": dn, "attached_to_pagetype": dt},
	)


def get_versions(pg: "Page") -> list[dict]:
	if not pg.meta.track_changes:
		return []
	return jingrow.get_all(
		"Version",
		filters=dict(ref_pagetype=pg.pagetype, pagename=str(pg.name)),
		fields=["name", "owner", "creation", "data"],
		limit=10,
		order_by="creation desc",
	)


@jingrow.whitelist()
def get_communications(pagetype, name, start=0, limit=20):
	from jingrow.utils import cint

	pg = jingrow.get_pg(pagetype, name)
	if not pg.has_permission("read"):
		raise jingrow.PermissionError

	return _get_communications(pagetype, name, cint(start), cint(limit))


def get_comments(pagetype: str, name: str, comment_type: str | list[str] = "Comment") -> list[jingrow._dict]:
	if isinstance(comment_type, list):
		comment_types = comment_type

	elif comment_type == "share":
		comment_types = ["Shared", "Unshared"]

	elif comment_type == "assignment":
		comment_types = ["Assignment Completed", "Assigned"]

	elif comment_type == "attachment":
		comment_types = ["Attachment", "Attachment Removed"]

	else:
		comment_types = [comment_type]

	comments = jingrow.get_all(
		"Comment",
		fields=["name", "creation", "content", "owner", "comment_type"],
		filters={
			"reference_pagetype": pagetype,
			"reference_name": name,
			"comment_type": ["in", comment_types],
		},
	)

	# convert to markdown (legacy ?)
	for c in comments:
		if c.comment_type == "Comment":
			c.content = jingrow.utils.markdown(c.content)

	return comments


def get_point_logs(pagetype, pagename):
	return jingrow.get_all(
		"Energy Point Log",
		filters={"reference_pagetype": pagetype, "reference_name": pagename, "type": ["!=", "Review"]},
		fields=["*"],
	)


def _get_communications(pagetype, name, start=0, limit=20):
	communications = get_communication_data(pagetype, name, start, limit)
	emails = get_email_data(pagetype, name, start, limit)
	for c in communications:
		if c.communication_type in ("Communication", "Automated Message"):
			c.attachments = json.dumps(
				jingrow.get_all(
					"File",
					fields=["file_url", "is_private"],
					filters={"attached_to_pagetype": "Communication", "attached_to_name": c.name},
				)
			)

	for e in emails:
		e.communication_type = "Communication"
		e.communication_medium = "Email"
		e.attachments = json.dumps(
			jingrow.get_all(
				"File",
				fields=["file_url", "is_private"],
				filters={"attached_to_pagetype": "Email", "attached_to_name": e.name},
			)
		)

	# Merge and sort by communication_date
	combined = list(communications) + list(emails)
	combined.sort(key=lambda x: x.communication_date or x.creation, reverse=True)
	return combined[:limit]


def get_communication_data(
	pagetype, name, start=0, limit=20, after=None, fields=None, group_by=None, as_dict=True
):
	"""Returns list of communications for a given document"""
	if not fields:
		fields = """
			C.name, C.communication_type, C.communication_medium,
			C.comment_type, C.communication_date, C.content,
			C.sender, C.sender_full_name, C.cc, C.bcc,
			C.creation AS creation, C.subject, C.delivery_status,
			C._liked_by, C.reference_pagetype, C.reference_name,
			C.read_by_recipient, C.rating, C.recipients
		"""

	conditions = ""
	if after:
		# find after a particular date
		conditions += f"""
			AND C.communication_date > {after}
		"""

	if pagetype == "User":
		conditions += """
			AND NOT (C.reference_pagetype='User' AND C.communication_type='Communication')
		"""

	# communications linked to reference_pagetype
	part1 = f"""
		SELECT {fields}
		FROM `tabCommunication` as C
		WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message')
		AND (C.reference_pagetype = %(pagetype)s AND C.reference_name = %(name)s)
		{conditions}
	"""

	# communications linked in Timeline Links
	part2 = f"""
		SELECT {fields}
		FROM `tabCommunication` as C
		INNER JOIN `tabCommunication Link` ON C.name=`tabCommunication Link`.parent
		WHERE C.communication_type IN ('Communication', 'Feedback', 'Automated Message')
		AND `tabCommunication Link`.link_pagetype = %(pagetype)s AND `tabCommunication Link`.link_name = %(name)s
		{conditions}
	"""

	return jingrow.db.sql(
		"""
		SELECT *
		FROM (({part1}) UNION ({part2})) AS combined
		{group_by}
		ORDER BY communication_date DESC
		LIMIT %(limit)s
		OFFSET %(start)s
	""".format(part1=part1, part2=part2, group_by=(group_by or "")),
		dict(
			pagetype=pagetype,
			name=name,
			start=jingrow.utils.cint(start),
			limit=limit,
		),
		as_dict=as_dict,
	)


def get_email_data(
	pagetype, name, start=0, limit=20, after=None, fields=None, group_by=None, as_dict=True
):
	"""Returns list of emails for a given document"""
	if not fields:
		fields = """
			E.name, 'Communication' as communication_type, 'Email' as communication_medium,
			'' as comment_type, E.communication_date, E.content,
			E.sender, E.sender_full_name, E.cc, E.bcc,
			E.creation AS creation, E.subject, E.delivery_status,
			E._liked_by, E.reference_pagetype, E.reference_name,
			E.read_by_recipient, 0 as rating, E.recipients
		"""

	conditions = ""
	if after:
		conditions += """
			AND E.communication_date > {after}
		""".format(after=after)

	# emails linked to reference_pagetype
	part1 = f"""
		SELECT {fields}
		FROM `tabEmail` as E
		WHERE (E.reference_pagetype = %(pagetype)s AND E.reference_name = %(name)s)
		{conditions}
	"""

	# emails linked in Email Links (uses Communication Link table)
	part2 = f"""
		SELECT {fields}
		FROM `tabEmail` as E
		INNER JOIN `tabCommunication Link` ON E.name=`tabCommunication Link`.parent
		WHERE `tabCommunication Link`.link_pagetype = %(pagetype)s AND `tabCommunication Link`.link_name = %(name)s
		{conditions}
	"""

	return jingrow.db.sql(
		"""
		SELECT *
		FROM (({part1}) UNION ({part2})) AS combined
		{group_by}
		ORDER BY communication_date DESC
		LIMIT %(limit)s
		OFFSET %(start)s
	""".format(part1=part1, part2=part2, group_by=(group_by or "")),
		dict(
			pagetype=pagetype,
			name=name,
			start=jingrow.utils.cint(start),
			limit=limit,
		),
		as_dict=as_dict,
	)


def get_assignments(dt, dn):
	return jingrow.get_all(
		"ToDo",
		fields=["name", "allocated_to as owner", "description", "status"],
		filters={
			"reference_type": dt,
			"reference_name": dn,
			"status": ("not in", ("Cancelled", "Closed")),
			"allocated_to": ("is", "set"),
		},
	)


def run_onload(pg):
	pg.set("__onload", jingrow._dict())
	pg.run_method("onload")


def get_view_logs(pg: "Page") -> list[dict]:
	"""get and return the latest view logs if available"""
	if not pg.meta.track_views:
		return []

	return jingrow.get_all(
		"View Log",
		filters={
			"reference_pagetype": pg.pagetype,
			"reference_name": pg.name,
		},
		fields=["name", "creation", "owner"],
		order_by="creation desc",
	)


def get_tags(pagetype: str, name: str) -> str:
	tags = jingrow.get_all(
		"Tag Link",
		filters={"document_type": pagetype, "document_name": name},
		fields=["tag"],
		pluck="tag",
	)

	return ",".join(tags)


def get_document_email(pagetype, name):
	email = get_automatic_email_link()
	if not email:
		return None

	email = email.split("@")
	return f"{email[0]}+{quote(pagetype)}={quote(cstr(name))}@{email[1]}"


def get_automatic_email_link():
	return jingrow.db.get_value(
		"Email Account", {"enable_incoming": 1, "enable_automatic_linking": 1}, "email_id"
	)


def get_additional_timeline_content(pagetype, pagename):
	contents = []
	hooks = jingrow.get_hooks().get("additional_timeline_content", {})
	methods_for_all_pagetype = hooks.get("*", [])
	methods_for_current_pagetype = hooks.get(pagetype, [])

	for method in methods_for_all_pagetype + methods_for_current_pagetype:
		contents.extend(jingrow.get_attr(method)(pagetype, pagename) or [])

	return contents


def set_link_titles(pg):
	link_titles = {}
	link_titles.update(get_title_values_for_link_and_dynamic_link_fields(pg))
	link_titles.update(get_title_values_for_table_and_multiselect_fields(pg))

	send_link_titles(link_titles)


def get_title_values_for_link_and_dynamic_link_fields(pg, link_fields=None):
	link_titles = {}

	if not link_fields:
		meta = jingrow.get_meta(pg.pagetype)
		link_fields = meta.get_link_fields() + meta.get_dynamic_link_fields()

	for field in link_fields:
		link_pagename = getattr(pg, field.fieldname, None)

		if not link_pagename:
			continue

		pagetype = field.options if field.fieldtype == "Link" else pg.get(field.options)

		meta = jingrow.get_meta(pagetype)
		if not meta or not (meta.title_field and meta.show_title_field_in_link):
			continue

		link_title = jingrow.db.get_value(pagetype, link_pagename, meta.title_field, cache=True, order_by=None)
		link_titles.update({pagetype + "::" + link_pagename: link_title})

	return link_titles


def get_title_values_for_table_and_multiselect_fields(pg, table_fields=None):
	link_titles = {}

	if not table_fields:
		meta = jingrow.get_meta(pg.pagetype)
		table_fields = meta.get_table_fields()

	for field in table_fields:
		if not pg.get(field.fieldname):
			continue

		for value in pg.get(field.fieldname):
			link_titles.update(get_title_values_for_link_and_dynamic_link_fields(value))

	return link_titles


def send_link_titles(link_titles):
	"""Append link titles dict in `jingrow.local.response`."""
	if "_link_titles" not in jingrow.local.response:
		jingrow.local.response["_link_titles"] = {}

	jingrow.local.response["_link_titles"].update(link_titles)


def update_user_info(pageinfo):
	users = set()

	users.update(d.sender for d in pageinfo.communications)
	users.update(d.user for d in pageinfo.shared)
	users.update(d.owner for d in pageinfo.assignments)
	users.update(d.owner for d in pageinfo.views)
	users.update(d.owner for d in pageinfo.workflow_logs)
	users.update(d.owner for d in pageinfo.like_logs)
	users.update(d.owner for d in pageinfo.info_logs)
	users.update(d.owner for d in pageinfo.attachment_logs)
	users.update(d.owner for d in pageinfo.assignment_logs)
	users.update(d.owner for d in pageinfo.comments)

	jingrow.utils.add_user_info(users, pageinfo.user_info)


@jingrow.whitelist()
def get_user_info_for_viewers(users):
	user_info = {}
	for user in json.loads(users):
		jingrow.utils.add_user_info(user, user_info)

	return user_info
