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

import json

from bs4 import BeautifulSoup

import jingrow
from jingrow import _
from jingrow.desk.pagetype.notification_log.notification_log import (
	enqueue_create_notification,
	get_title,
	get_title_html,
)
from jingrow.desk.pagetype.notification_settings.notification_settings import (
	get_subscribed_documents,
)
from jingrow.utils import get_fullname


@jingrow.whitelist()
@jingrow.read_only()
def get_notifications():
	out = {
		"open_count_pagetype": {},
		"targets": {},
	}
	if jingrow.flags.in_install or not jingrow.db.get_single_value("System Settings", "setup_complete"):
		return out

	config = get_notification_config()

	if not config:
		return out

	groups = list(config.get("for_pagetype")) + list(config.get("for_module"))

	notification_count = {}
	notification_percent = {}

	for name in groups:
		count = jingrow.cache.hget("notification_count:" + name, jingrow.session.user)
		if count is not None:
			notification_count[name] = count

	out["open_count_pagetype"] = get_notifications_for_pagetypes(config, notification_count)
	out["targets"] = get_notifications_for_targets(config, notification_percent)

	return out


def get_notifications_for_pagetypes(config, notification_count):
	"""Notifications for PageTypes"""
	can_read = jingrow.get_user().get_can_read()
	open_count_pagetype = {}

	for d in config.for_pagetype:
		if d in can_read:
			condition = config.for_pagetype[d]

			if d in notification_count:
				open_count_pagetype[d] = notification_count[d]
			else:
				try:
					if isinstance(condition, dict):
						result = jingrow.get_list(
							d, fields=["count(*) as count"], filters=condition, ignore_ifnull=True
						)[0].count
					else:
						result = jingrow.get_attr(condition)()

				except jingrow.PermissionError:
					jingrow.clear_messages()
					pass
					# jingrow.msgprint("Permission Error in notifications for {0}".format(d))

				except Exception as e:
					# OperationalError: (1412, 'Table definition has changed, please retry transaction')
					# InternalError: (1684, 'Table definition is being modified by concurrent DDL statement')
					if e.args and e.args[0] not in (1412, 1684):
						raise

				else:
					open_count_pagetype[d] = result
					jingrow.cache.hset("notification_count:" + d, jingrow.session.user, result)

	return open_count_pagetype


def get_notifications_for_targets(config, notification_percent):
	"""Notifications for pg targets"""
	can_read = jingrow.get_user().get_can_read()
	pg_target_percents = {}

	# pg_target_percents = {
	# 	"Company": {
	# 		"Acme": 87,
	# 		"RobotsRUs": 50,
	# 	}, {}...
	# }

	for pagetype in config.targets:
		if pagetype in can_read:
			if pagetype in notification_percent:
				pg_target_percents[pagetype] = notification_percent[pagetype]
			else:
				pg_target_percents[pagetype] = {}
				d = config.targets[pagetype]
				condition = d["filters"]
				target_field = d["target_field"]
				value_field = d["value_field"]
				try:
					if isinstance(condition, dict):
						pg_list = jingrow.get_list(
							pagetype,
							fields=["name", target_field, value_field],
							filters=condition,
							limit_page_length=100,
							ignore_ifnull=True,
						)

				except jingrow.PermissionError:
					jingrow.clear_messages()
					pass
				except Exception as e:
					if e.args[0] not in (1412, 1684):
						raise

				else:
					for pg in pg_list:
						value = pg[value_field]
						target = pg[target_field]
						pg_target_percents[pagetype][pg.name] = (
							(value / target * 100) if value < target else 100
						)

	return pg_target_percents


def clear_notifications(user=None):
	if jingrow.flags.in_install:
		return
	config = get_notification_config()

	if not config:
		return

	for_pagetype = list(config.get("for_pagetype")) if config.get("for_pagetype") else []
	for_module = list(config.get("for_module")) if config.get("for_module") else []
	groups = for_pagetype + for_module

	for name in groups:
		if user:
			jingrow.cache.hdel("notification_count:" + name, user)
		else:
			jingrow.cache.delete_key("notification_count:" + name)


def clear_notification_config(user):
	jingrow.cache.hdel("notification_config", user)


def delete_notification_count_for(pagetype):
	jingrow.cache.delete_key("notification_count:" + pagetype)


def clear_pagetype_notifications(pg, method=None, *args, **kwargs):
	config = get_notification_config()
	if not config:
		return
	if isinstance(pg, str):
		pagetype = pg  # assuming pagetype name was passed directly
	else:
		pagetype = pg.pagetype

	if pagetype in config.for_pagetype:
		delete_notification_count_for(pagetype)
		return


@jingrow.whitelist()
def get_notification_info():
	config = get_notification_config()
	out = get_notifications()
	can_read = jingrow.get_user().get_can_read()
	conditions = {}
	module_pagetypes = {}
	pagetype_info = dict(jingrow.db.sql("""select name, module from tabPageType"""))

	for d in list(set(can_read + list(config.for_pagetype))):
		if d in config.for_pagetype:
			conditions[d] = config.for_pagetype[d]

		if d in pagetype_info:
			module_pagetypes.setdefault(pagetype_info[d], []).append(d)

	out.update(
		{
			"conditions": conditions,
			"module_pagetypes": module_pagetypes,
		}
	)

	return out


def get_notification_config():
	user = jingrow.session.user or "Guest"

	def _get():
		subscribed_documents = get_subscribed_documents()
		config = jingrow._dict()
		hooks = jingrow.get_hooks()
		if hooks:
			for notification_config in hooks.notification_config:
				nc = jingrow.get_attr(notification_config)()
				for key in ("for_pagetype", "for_module", "for_other", "targets"):
					config.setdefault(key, {})
					if key == "for_pagetype":
						if len(subscribed_documents) > 0:
							key_config = nc.get(key, {})
							subscribed_pgs_config = jingrow._dict()
							for document in subscribed_documents:
								if key_config.get(document):
									subscribed_pgs_config[document] = key_config.get(document)
							config[key].update(subscribed_pgs_config)
						else:
							config[key].update(nc.get(key, {}))
					else:
						config[key].update(nc.get(key, {}))
		return config

	return jingrow.cache.hget("notification_config", user, _get)


def get_filters_for(pagetype):
	"""get open filters for pagetype"""
	config = get_notification_config()
	pagetype_config = config.get("for_pagetype").get(pagetype, {})
	return None if isinstance(pagetype_config, str) else pagetype_config


@jingrow.whitelist()
@jingrow.read_only()
def get_open_count(pagetype: str, name: str, items=None):
	"""Get count for internal and external links for given transactions

	:param pagetype: Reference PageType
	:param name: Reference Name
	:param items: Optional list of transactions (json/dict)"""

	if jingrow.flags.in_migrate or jingrow.flags.in_install:
		return {"count": []}

	pg = jingrow.get_pg(pagetype, name)
	pg.check_permission()
	meta = pg.meta
	links = meta.get_dashboard_data()

	# compile all items in a list
	if items is None:
		items = []
		for group in links.transactions:
			items.extend(group.get("items"))

	if not isinstance(items, list):
		items = json.loads(items)

	out = {
		"external_links_found": [],
		"internal_links_found": [],
	}

	for d in items:
		internal_link_for_pagetype = links.get("internal_links", {}).get(d) or links.get(
			"internal_and_external_links", {}
		).get(d)
		if internal_link_for_pagetype:
			internal_links_data_for_d = get_internal_links(pg, internal_link_for_pagetype, d)
			if internal_links_data_for_d["count"]:
				out["internal_links_found"].append(internal_links_data_for_d)
			else:
				try:
					external_links_data_for_d = get_external_links(d, name, links)
					out["external_links_found"].append(external_links_data_for_d)
				except Exception:
					out["external_links_found"].append({"pagetype": d, "open_count": 0, "count": 0})
		else:
			external_links_data_for_d = get_external_links(d, name, links)
			out["external_links_found"].append(external_links_data_for_d)

	out = {
		"count": out,
	}

	if not meta.custom:
		module = jingrow.get_meta_module(pagetype)
		if hasattr(module, "get_timeline_data"):
			out["timeline_data"] = module.get_timeline_data(pagetype, name)

	return out


def get_internal_links(pg, link, link_pagetype):
	names = []
	data = {"pagetype": link_pagetype}

	if isinstance(link, str):
		# get internal links in parent document
		value = pg.get(link)
		if value and value not in names:
			names.append(value)
	elif isinstance(link, list):
		# get internal links in child documents
		table_fieldname, link_fieldname = link
		for row in pg.get(table_fieldname) or []:
			value = row.get(link_fieldname)
			if value and value not in names:
				names.append(value)

	data["open_count"] = 0
	data["count"] = len(names)
	data["names"] = names

	return data


def get_external_links(pagetype, name, links):
	fieldname = links.get("non_standard_fieldnames", {}).get(pagetype, links.get("fieldname"))
	filters = {fieldname: name}

	# updating filters based on dynamic_links
	if dynamic_link_filters := get_dynamic_link_filters(pagetype, links, fieldname):
		filters.update(dynamic_link_filters)

	total_count = get_pg_count(pagetype, filters)

	open_count = 0
	if open_count_filters := get_filters_for(pagetype):
		filters.update(open_count_filters)
		open_count = get_pg_count(pagetype, filters)

	return {"pagetype": pagetype, "count": total_count, "open_count": open_count}


def get_pg_count(pagetype, filters):
	return len(
		jingrow.get_all(
			pagetype,
			fields="name",
			filters=filters,
			limit=100,
			distinct=True,
			ignore_ifnull=True,
			order_by=None,
		)
	)


def get_dynamic_link_filters(pagetype, links, fieldname):
	"""
	- Updating filters based on dynamic_links specified in the dashboard data.
	- Eg: "dynamic_links": {"fieldname": ["dynamic_fieldvalue", "dynamic_fieldname"]},
	"""
	dynamic_link = links.get("dynamic_links", {}).get(fieldname)

	if not dynamic_link:
		return

	pagetype_value, pagetype_fieldname = dynamic_link

	meta = jingrow.get_meta(pagetype)
	if not meta.has_field(pagetype_fieldname):
		return

	return {pagetype_fieldname: pagetype_value}


def notify_mentions(ref_pagetype, ref_name, content):
	if ref_pagetype and ref_name and content:
		mentions = extract_mentions(content)

		if not mentions:
			return

		sender_fullname = get_fullname(jingrow.session.user)
		title = get_title(ref_pagetype, ref_name)

		recipients = [
			jingrow.db.get_value(
				"User",
				{"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1},
				"email",
			)
			for name in mentions
		]

		notification_message = _("""{0} mentioned you in a comment in {1} {2}""").format(
			jingrow.bold(sender_fullname), jingrow.bold(ref_pagetype), get_title_html(title)
		)

		notification_pg = {
			"type": "Mention",
			"document_type": ref_pagetype,
			"document_name": ref_name,
			"subject": notification_message,
			"from_user": jingrow.session.user,
			"email_content": content,
		}

		enqueue_create_notification(recipients, notification_pg)


def extract_mentions(txt):
	"""Find all instances of @mentions in the html."""
	soup = BeautifulSoup(txt, "html.parser")
	emails = []
	for mention in soup.find_all(class_="mention"):
		if mention.get("data-is-group") == "true":
			try:
				user_group = jingrow.get_cached_pg("User Group", mention["data-id"])
				emails += [d.user for d in user_group.user_group_members]
			except jingrow.DoesNotExistError:
				pass
			continue
		email = mention["data-id"]
		emails.append(email)

	return emails
