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

import jingrow
from jingrow import _
from jingrow.model import child_table_fields, default_fields, table_fields
from jingrow.utils import cstr


@jingrow.whitelist()
def make_mapped_pg(method, source_name, selected_children=None, args=None):
	"""Returns the mapped document calling the given mapper method.
	Sets selected_children as flags for the `get_mapped_pg` method.

	Called from `open_mapped_pg` from create_new.js"""
	method = jingrow.get_attr(jingrow.override_whitelisted_method(method))

	if method not in jingrow.whitelisted:
		raise jingrow.PermissionError

	if selected_children:
		selected_children = json.loads(selected_children)

	if args:
		jingrow.flags.args = jingrow._dict(json.loads(args))

	jingrow.flags.selected_children = selected_children or None

	return method(source_name)


@jingrow.whitelist()
def map_pgs(method, source_names, target_pg, args=None):
	'''Returns the mapped document calling the given mapper method
	with each of the given source pgs on the target pg

	:param args: Args as string to pass to the mapper method
	E.g. args: "{ 'supplier': 'XYZ' }"'''
	method = jingrow.get_attr(jingrow.override_whitelisted_method(method))
	if method not in jingrow.whitelisted:
		raise jingrow.PermissionError

	for src in json.loads(source_names):
		_args = (src, target_pg, json.loads(args)) if args else (src, target_pg)
		target_pg = method(*_args)
	return target_pg


def get_mapped_pg(
	from_pagetype,
	from_pagename,
	table_maps,
	target_pg=None,
	postprocess=None,
	ignore_permissions=False,
	ignore_child_tables=False,
	cached=False,
):
	apply_strict_user_permissions = jingrow.get_system_settings("apply_strict_user_permissions")

	# main
	if not target_pg:
		target_pg = jingrow.new_pg(table_maps[from_pagetype]["pagetype"])
	elif isinstance(target_pg, str):
		target_pg = jingrow.get_pg(json.loads(target_pg))

	if (
		not apply_strict_user_permissions
		and not ignore_permissions
		and not target_pg.has_permission("create")
	):
		target_pg.raise_no_permission_to("create")

	if cached:
		source_pg = jingrow.get_cached_pg(from_pagetype, from_pagename)
	else:
		source_pg = jingrow.get_pg(from_pagetype, from_pagename)

	if not ignore_permissions:
		if not source_pg.has_permission("read"):
			source_pg.raise_no_permission_to("read")

	target_pg.run_method("before_mapping", source_pg, table_maps)

	map_pg(source_pg, target_pg, table_maps[source_pg.pagetype])

	row_exists_for_parentfield = {}

	# children
	if not ignore_child_tables:
		for df in source_pg.meta.get_table_fields():
			source_child_pagetype = df.options
			table_map = table_maps.get(source_child_pagetype)

			# if table_map isn't explicitly specified check if both source and target have the same fieldname and same table options and both of them don't have no_copy
			if not table_map:
				target_df = target_pg.meta.get_field(df.fieldname)
				if target_df:
					target_child_pagetype = target_df.options
					if (
						target_df
						and target_child_pagetype == source_child_pagetype
						and not df.no_copy
						and not target_df.no_copy
					):
						table_map = {"pagetype": target_child_pagetype}

			if table_map:
				target_child_pagetype = table_map["pagetype"]
				target_parentfield = target_pg.get_parentfield_of_pagetype(target_child_pagetype)

				if table_map.get("reset_value"):
					setattr(target_pg, target_parentfield, [])

				for source_d in source_pg.get(df.fieldname):
					if "condition" in table_map:
						if not table_map["condition"](source_d):
							continue

					# if children are selected (checked from UI) for this table type,
					# and this record is not in the selected children, then continue
					if (
						jingrow.flags.selected_children
						and (df.fieldname in jingrow.flags.selected_children)
						and source_d.name not in jingrow.flags.selected_children[df.fieldname]
					):
						continue

					# does row exist for a parentfield?
					if target_parentfield not in row_exists_for_parentfield:
						row_exists_for_parentfield[target_parentfield] = (
							True if target_pg.get(target_parentfield) else False
						)

					if table_map.get("ignore"):
						continue

					if table_map.get("add_if_empty") and row_exists_for_parentfield.get(target_parentfield):
						continue

					if table_map.get("filter") and table_map.get("filter")(source_d):
						continue

					map_child_pg(source_d, target_pg, table_map, source_pg)

	if postprocess:
		postprocess(source_pg, target_pg)

	target_pg.run_method("after_mapping", source_pg)
	target_pg.set_onload("load_after_mapping", True)

	if apply_strict_user_permissions and not ignore_permissions and not target_pg.has_permission("create"):
		target_pg.raise_no_permission_to("create")

	return target_pg


def map_pg(source_pg, target_pg, table_map, source_parent=None):
	if table_map.get("validation"):
		for key, condition in table_map["validation"].items():
			if condition[0] == "=" and source_pg.get(key) != condition[1]:
				jingrow.throw(
					_("Cannot map because following condition fails:") + f" {key}={cstr(condition[1])}"
				)

	map_fields(source_pg, target_pg, table_map, source_parent)

	if "postprocess" in table_map:
		table_map["postprocess"](source_pg, target_pg, source_parent)


def map_fields(source_pg, target_pg, table_map, source_parent):
	no_copy_fields = set(
		[
			d.fieldname
			for d in source_pg.meta.get("fields")
			if (d.no_copy == 1 or d.fieldtype in table_fields)
		]
		+ [
			d.fieldname
			for d in target_pg.meta.get("fields")
			if (d.no_copy == 1 or d.fieldtype in table_fields)
		]
		+ list(default_fields)
		+ list(child_table_fields)
		+ list(table_map.get("field_no_map", []))
	)

	for df in target_pg.meta.get("fields"):
		if df.fieldname not in no_copy_fields:
			# map same fields
			val = source_pg.get(df.fieldname)
			if val not in (None, ""):
				target_pg.set(df.fieldname, val)

			elif df.fieldtype == "Link":
				if not target_pg.get(df.fieldname):
					# map link fields having options == source pagetype
					if df.options == source_pg.pagetype:
						target_pg.set(df.fieldname, source_pg.name)

					elif source_parent and df.options == source_parent.pagetype:
						target_pg.set(df.fieldname, source_parent.name)

	# map other fields
	field_map = table_map.get("field_map")

	if field_map:
		if isinstance(field_map, dict):
			for source_key, target_key in field_map.items():
				val = source_pg.get(source_key)
				if val not in (None, ""):
					target_pg.set(target_key, val)
		else:
			for fmap in field_map:
				val = source_pg.get(fmap[0])
				if val not in (None, ""):
					target_pg.set(fmap[1], val)

	# map idx
	if source_pg.idx:
		target_pg.idx = source_pg.idx

	# add fetch
	for df in target_pg.meta.get("fields", {"fieldtype": "Link"}):
		if target_pg.get(df.fieldname):
			map_fetch_fields(target_pg, df, no_copy_fields)


def map_fetch_fields(target_pg, df, no_copy_fields):
	linked_pg = None

	# options should be like "link_fieldname.fieldname_in_liked_pg"
	for fetch_df in target_pg.meta.get("fields", {"fetch_from": f"^{df.fieldname}."}):
		if not (fetch_df.fieldtype == "Read Only" or fetch_df.read_only):
			continue

		if (
			not target_pg.get(fetch_df.fieldname) or fetch_df.fieldtype == "Read Only"
		) and fetch_df.fieldname not in no_copy_fields:
			source_fieldname = fetch_df.fetch_from.split(".")[1]

			if not linked_pg:
				try:
					linked_pg = jingrow.get_pg(df.options, target_pg.get(df.fieldname))
				except Exception:
					return

			val = linked_pg.get(source_fieldname)

			if val not in (None, ""):
				target_pg.set(fetch_df.fieldname, val)


def map_child_pg(source_d, target_parent, table_map, source_parent=None):
	target_child_pagetype = table_map["pagetype"]
	target_parentfield = target_parent.get_parentfield_of_pagetype(target_child_pagetype)
	target_d = jingrow.new_pg(target_child_pagetype, parent_pg=target_parent, parentfield=target_parentfield)

	map_pg(source_d, target_d, table_map, source_parent)

	target_d.idx = None
	target_parent.append(target_parentfield, target_d)
	return target_d
