# Copyright (c) 2015, InfluxFramework LLC
# License: MIT. See LICENSE

import json

from bs4 import BeautifulSoup

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


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

	config = get_notification_config()

	if not config:
		return out

	groups = list(config.get("for_doctype")) + list(config.get("for_module"))
	cache = influxframework.cache()

	notification_count = {}
	notification_percent = {}

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

	out["open_count_doctype"] = get_notifications_for_doctypes(config, notification_count)
	out["targets"] = get_notifications_for_targets(config, notification_percent)

	return out


def get_notifications_for_doctypes(config, notification_count):
	"""Notifications for DocTypes"""
	can_read = influxframework.get_user().get_can_read()
	open_count_doctype = {}

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

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

				except influxframework.PermissionError:
					influxframework.clear_messages()
					pass
					# influxframework.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_doctype[d] = result
					influxframework.cache().hset("notification_count:" + d, influxframework.session.user, result)

	return open_count_doctype


def get_notifications_for_targets(config, notification_percent):
	"""Notifications for doc targets"""
	can_read = influxframework.get_user().get_can_read()
	doc_target_percents = {}

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

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

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

				else:
					for doc in doc_list:
						value = doc[value_field]
						target = doc[target_field]
						doc_target_percents[doctype][doc.name] = (value / target * 100) if value < target else 100

	return doc_target_percents


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

	if not config:
		return

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

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

	influxframework.publish_realtime("clear_notifications")


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


def delete_notification_count_for(doctype):
	influxframework.cache().delete_key("notification_count:" + doctype)
	influxframework.publish_realtime("clear_notifications")


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

	if doctype in config.for_doctype:
		delete_notification_count_for(doctype)
		return


@influxframework.whitelist()
def get_notification_info():
	config = get_notification_config()
	out = get_notifications()
	can_read = influxframework.get_user().get_can_read()
	conditions = {}
	module_doctypes = {}
	doctype_info = dict(influxframework.db.sql("""select name, module from tabDocType"""))

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

		if d in doctype_info:
			module_doctypes.setdefault(doctype_info[d], []).append(d)

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

	return out


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

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

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


def get_filters_for(doctype):
	"""get open filters for doctype"""
	config = get_notification_config()
	doctype_config = config.get("for_doctype").get(doctype, {})
	filters = doctype_config if not isinstance(doctype_config, str) else None

	return filters


@influxframework.whitelist()
@influxframework.read_only()
def get_open_count(doctype, name, items=None):
	"""Get open count for given transactions and filters

	:param doctype: Reference DocType
	:param name: Reference Name
	:param transactions: List of transactions (json/dict)
	:param filters: optional filters (json/list)"""

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

	doc = influxframework.get_doc(doctype, name)
	doc.check_permission()
	meta = doc.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 = []
	for d in items:
		if d in links.get("internal_links", {}):
			continue

		filters = get_filters_for(d)
		fieldname = links.get("non_standard_fieldnames", {}).get(d, links.get("fieldname"))
		data = {"name": d}
		if filters:
			# get the fieldname for the current document
			# we only need open documents related to the current document
			filters[fieldname] = name
			total = len(
				influxframework.get_all(d, fields="name", filters=filters, limit=100, distinct=True, ignore_ifnull=True)
			)
			data["open_count"] = total

		total = len(
			influxframework.get_all(
				d, fields="name", filters={fieldname: name}, limit=100, distinct=True, ignore_ifnull=True
			)
		)
		data["count"] = total
		out.append(data)

	out = {
		"count": out,
	}

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

	return out


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

		if not mentions:
			return

		sender_fullname = get_fullname(influxframework.session.user)
		title = get_title(ref_doctype, ref_name)

		recipients = [
			influxframework.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(
			influxframework.bold(sender_fullname), influxframework.bold(ref_doctype), get_title_html(title)
		)

		notification_doc = {
			"type": "Mention",
			"document_type": ref_doctype,
			"document_name": ref_name,
			"subject": notification_message,
			"from_user": influxframework.session.user,
			"email_content": content,
		}

		enqueue_create_notification(recipients, notification_doc)


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 = influxframework.get_cached_doc("User Group", mention["data-id"])
				emails += [d.user for d in user_group.user_group_members]
			except influxframework.DoesNotExistError:
				pass
			continue
		email = mention["data-id"]
		emails.append(email)

	return emails
