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

import jingrow

# select pagetypes that are accessed by the user (not read_only) first, so that the
# the validation message shows the user-facing pagetype first.
# For example Journal Entry should be validated before GL Entry (which is an internal pagetype)

dynamic_link_queries = [
	"""select `tabPageField`.parent,
		`tabPageType`.read_only, `tabPageType`.in_create,
		`tabPageField`.fieldname, `tabPageField`.options
	from `tabPageField`, `tabPageType`
	where `tabPageField`.fieldtype='Dynamic Link' and
	`tabPageType`.`name`=`tabPageField`.parent and `tabPageType`.is_virtual = 0
	order by `tabPageType`.read_only, `tabPageType`.in_create""",
	"""select `tabCustom Field`.dt as parent,
		`tabPageType`.read_only, `tabPageType`.in_create,
		`tabCustom Field`.fieldname, `tabCustom Field`.options
	from `tabCustom Field`, `tabPageType`
	where `tabCustom Field`.fieldtype='Dynamic Link' and
	`tabPageType`.`name`=`tabCustom Field`.dt
	order by `tabPageType`.read_only, `tabPageType`.in_create""",
]


def get_dynamic_link_map(for_delete=False):
	"""Build a map of all dynamically linked tables. For example,
	        if Note is dynamically linked to ToDo, the function will return
	        `{"Note": ["ToDo"], "Sales Invoice": ["Journal Entry Detail"]}`

	Note: Will not map single pagetypes
	"""
	if getattr(jingrow.local, "dynamic_link_map", None) is None or jingrow.flags.in_test:
		# Build from scratch
		dynamic_link_map = {}
		for df in get_dynamic_links():
			meta = jingrow.get_meta(df.parent)
			if meta.issingle:
				# always check in Single PageTypes
				dynamic_link_map.setdefault(meta.name, []).append(df)
			else:
				try:
					links = fetch_distinct_link_pagetypes(df.parent, df.options)
					for pagetype in links:
						dynamic_link_map.setdefault(pagetype, []).append(df)
				except jingrow.db.TableMissingError:
					pass

		jingrow.local.dynamic_link_map = dynamic_link_map
	return jingrow.local.dynamic_link_map


def get_dynamic_links():
	"""Return list of dynamic link fields as PageField.
	Uses cache if possible"""
	df = []
	for query in dynamic_link_queries:
		df += jingrow.db.sql(query, as_dict=True)
	return df


def _dynamic_link_map_key(pagetype, fieldname):
	return f"dynamic_link_map::{pagetype}::{fieldname}"


def fetch_distinct_link_pagetypes(pagetype: str, fieldname: str):
	"""Return all unique pagetypes a dynamic link is linking against.
	Note:
	- results are cached and can *possibly be outdated*
	- cache gets updated when a document with different document link is discovered
	- raw queries adding dynamic link won't update this cache
	- cache miss can often be VERY expensive on large table.
	"""

	key = _dynamic_link_map_key(pagetype, fieldname)
	pagetypes = jingrow.cache.get_value(key)

	if pagetypes is None:
		pagetypes = jingrow.db.sql(f"""select distinct `{fieldname}` from `tab{pagetype}`""", pluck=True)
		jingrow.cache.set_value(key, pagetypes, expires_in_sec=12 * 60 * 60)

	return pagetypes


def invalidate_distinct_link_pagetypes(pagetype: str, fieldname: str, linked_pagetype: str):
	"""If new linked pagetype is discovered for a dynamic link then cache is evicted."""

	key = _dynamic_link_map_key(pagetype, fieldname)
	pagetypes = jingrow.cache.get_value(key)

	if pagetypes is None or not isinstance(pagetypes, list):
		return

	if linked_pagetype not in pagetypes:
		# Note: Do NOT "update" cache because it can lead to concurrency bugs.
		jingrow.cache.delete_value(key)
		jingrow.db.after_commit.add(lambda: jingrow.cache.delete_value(key))
