# Copyright (c) 2019, JINGROW
# License: MIT. See LICENSE

import json
import re

import jingrow
from jingrow import _
from jingrow.core.utils import find
from jingrow.model.page import Page
from jingrow.utils import get_datetime, get_fullname, time_diff_in_hours
from jingrow.utils.user import get_system_managers
from jingrow.utils.verified_command import get_signed_params, verify_request


class PersonalDataDeletionRequest(Page):
	# begin: auto-generated types
	# This code is auto-generated. Do not modify anything in this block.

	from typing import TYPE_CHECKING

	if TYPE_CHECKING:
		from jingrow.types import DF
		from jingrow.website.pagetype.personal_data_deletion_step.personal_data_deletion_step import (
			PersonalDataDeletionStep,
		)

		anonymization_matrix: DF.Code | None
		deletion_steps: DF.Table[PersonalDataDeletionStep]
		email: DF.Data
		status: DF.Literal["Pending Verification", "Pending Approval", "On Hold", "Deleted"]

	# end: auto-generated types
	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)

		self.user_data_fields = jingrow.get_hooks("user_data_fields")
		self.full_match_privacy_pgs = [x for x in self.user_data_fields if x.get("redact_fields")]
		self.partial_privacy_pgs = [
			x for x in self.user_data_fields if x.get("partial") or not x.get("redact_fields")
		]
		self.anonymization_value_map = {
			"Code": "[REDACTED]: Removed due to Personal Data Deletion Request",
			"Data": "[REDACTED]",
			"Date": "1111-01-01",
			"Int": 0,
			"Phone": "+91 0000000000",
			"Name": "REDACTED",
		}

	def autoname(self):
		from jingrow.model.naming import set_name_from_naming_options

		pattern = re.compile(
			r"^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|"
			r"([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|"
			r"([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\."
			r"([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$"
		)
		domain = jingrow.local.site.replace("_", "-")
		site = domain if pattern.match(domain) else f"{domain}.com"
		autoname = f"format:deleted-user-{{####}}@{site}"
		set_name_from_naming_options(autoname, self)
		jingrow.utils.validate_email_address(self.email, throw=True)

	def after_insert(self):
		self.send_verification_mail()

	def generate_url_for_confirmation(self):
		params = {"email": self.email, "name": self.name, "host_name": jingrow.local.site}
		api = jingrow.utils.get_url(
			"/api/action/jingrow.website.pagetype.personal_data_deletion_request"
			".personal_data_deletion_request.confirm_deletion"
		)
		url = f"{api}?{get_signed_params(params)}"

		if jingrow.conf.developer_mode:
			print(f"URL generated for {self.pagetype} {self.name}: {url}")

		return url

	def disable_user(self):
		user = jingrow.get_pg("User", self.email)
		user.enabled = False
		user.save()

	def send_verification_mail(self):
		url = self.generate_url_for_confirmation()

		jingrow.sendmail(
			recipients=self.email,
			subject=_("Confirm Deletion of Account"),
			template="delete_data_confirmation",
			args={
				"email": self.email,
				"name": self.name,
				"host_name": jingrow.utils.get_url(),
				"link": url,
			},
			header=[_("Confirm Deletion of Account"), "green"],
		)

	def notify_system_managers(self):
		system_managers = get_system_managers(only_name=True)

		jingrow.sendmail(
			recipients=system_managers,
			subject=_("User {0} has requested for data deletion").format(self.email),
			template="data_deletion_approval",
			args={"user": self.email, "url": jingrow.utils.get_url(self.get_url())},
			header=[_("Approval Required"), "green"],
		)

	def validate_data_anonymization(self):
		jingrow.only_for("System Manager")

		if self.status != "Pending Approval":
			jingrow.throw(_("This request has not yet been approved by the user."))

	@jingrow.whitelist()
	def trigger_data_deletion(self):
		"""Redact user data defined in current site's hooks under `user_data_fields`"""
		self.validate_data_anonymization()
		self.disable_user()
		self.anonymize_data()
		self.notify_user_after_deletion()

	def anonymize_data(self):
		return jingrow.enqueue_pg(
			self.pagetype,
			self.name,
			"_anonymize_data",
			queue="long",
			timeout=3000,
			now=jingrow.flags.in_test,
		)

	def notify_user_after_deletion(self):
		jingrow.sendmail(
			recipients=self.email,
			subject=_("Your account has been deleted"),
			template="account_deletion_notification",
			args={
				"email": self.email,
				"host_name": jingrow.utils.get_url(),
			},
			header=[_("Your account has been deleted"), "green"],
		)

	def add_deletion_steps(self):
		if self.deletion_steps:
			return

		for step in self.full_match_privacy_pgs + self.partial_privacy_pgs:
			row_data = {
				"status": "Pending",
				"document_type": step.get("pagetype"),
				"partial": step.get("partial") or False,
				"fields": json.dumps(step.get("redact_fields", [])),
				"filtered_by": step.get("filtered_by") or "",
			}
			self.append("deletion_steps", row_data)

		self.anonymization_matrix = json.dumps(self.anonymization_value_map, indent=4)
		self.save()
		self.reload()

	def redact_partial_match_data(self, pagetype):
		self.__redact_partial_match_data(pagetype)
		self.rename_documents(pagetype)

	def rename_documents(self, pagetype):
		if not pagetype.get("rename"):
			return

		def new_name(email, number):
			email_user, domain = email.split("@")
			return f"{email_user}-{number}@{domain}"

		for i, name in enumerate(
			jingrow.get_all(
				pagetype["pagetype"],
				filters={pagetype.get("filter_by", "owner"): self.email},
				pluck="name",
			)
		):
			jingrow.rename_pg(
				pagetype["pagetype"], name, new_name(self.anon, i + 1), force=True, show_alert=False
			)

	def redact_full_match_data(self, ref, email):
		"""Replaces the entire field value by the values set in the anonymization_value_map"""
		filter_by = ref.get("filter_by", "owner")

		pgs = jingrow.get_all(
			ref["pagetype"],
			filters={filter_by: email},
			fields=["name", filter_by],
		)

		# skip if there are no Documents
		if not pgs:
			return

		self.anonymize_fields_dict = self.generate_anonymization_dict(ref)

		for pg in pgs:
			self.redact_pg(pg, ref)

	def generate_anonymization_dict(self, ref):
		anonymize_fields_dict = {}
		meta = jingrow.get_meta(ref["pagetype"])

		for field in ref.get("redact_fields", []):
			field_details = meta.get_field(field)

			if not field_details:
				print(f"Incorrect personal_field {field} defined in hooks")
				continue

			field_value = (
				self.anon
				if field_details.unique
				else (
					self.anonymization_value_map.get(field_details.options)
					or self.anonymization_value_map.get(field_details.fieldtype)
					or field
				)
			)
			anonymize_fields_dict[field] = field_value

		return anonymize_fields_dict

	def redact_pg(self, pg, ref):
		filter_by = ref.get("filter_by", "owner")
		meta = jingrow.get_meta(ref["pagetype"])
		filter_by_meta = meta.get_field(filter_by)

		if filter_by_meta and filter_by_meta.fieldtype != "Link":
			if self.email in pg[filter_by]:
				value = re.sub(self.full_name_regex, self.anonymization_value_map["Data"], pg[filter_by])
				value = re.sub(self.email_regex, self.anon, value)
				self.anonymize_fields_dict[filter_by] = value

		jingrow.db.set_value(
			ref["pagetype"],
			pg["name"],
			self.anonymize_fields_dict,
			modified_by="Administrator",
		)

		if ref.get("rename") and pg["name"] != self.anon:
			jingrow.rename_pg(ref["pagetype"], pg["name"], self.anon, force=True, show_alert=False)

	def _anonymize_data(self, email=None, anon=None, set_data=True, commit=False):
		email = email or self.email
		anon = anon or self.name

		if set_data:
			self.__set_anonymization_data(email, anon)

		self.add_deletion_steps()

		self.full_match_pagetypes = (
			x
			for x in self.full_match_privacy_pgs
			if filter(lambda x: x.document_type == x and x.status == "Pending", self.deletion_steps)
		)

		self.partial_match_pagetypes = (
			x
			for x in self.partial_privacy_pgs
			if filter(lambda x: x.document_type == x and x.status == "Pending", self.deletion_steps)
		)

		for pagetype in self.full_match_pagetypes:
			self.redact_full_match_data(pagetype, email)
			self.set_step_status(pagetype["pagetype"])
			if commit:
				jingrow.db.commit()

		for pagetype in self.partial_match_pagetypes:
			self.redact_partial_match_data(pagetype)
			self.set_step_status(pagetype["pagetype"])
			if commit:
				jingrow.db.commit()

		jingrow.rename_pg("User", email, anon, force=True, show_alert=False)
		self.db_set("status", "Deleted")

		if commit:
			jingrow.db.commit()

	def set_step_status(self, step, status="Deleted"):
		del_step = find(self.deletion_steps, lambda x: x.document_type == step and x.status != status)

		if not del_step:
			del_step = find(self.deletion_steps, lambda x: x.document_type == step)

		del_step.status = status
		self.save()
		self.reload()

	def __set_anonymization_data(self, email, anon):
		self.anon = anon or self.name
		self.full_name = get_fullname(email)
		self.email_regex = get_pattern(email)
		self.full_name_regex = get_pattern(self.full_name)
		self.is_full_name_set = email != self.full_name
		self.anonymization_value_map["Email"] = self.anon

	def __redact_partial_match_data(self, pagetype):
		match_fields = []
		editable_text_fields = {
			"Small Text",
			"Text",
			"Text Editor",
			"Code",
			"HTML Editor",
			"Markdown Editor",
			"Long Text",
			"Data",
		}

		for df in jingrow.get_meta(pagetype["pagetype"]).fields:
			if df.fieldtype not in editable_text_fields:
				continue

			match_fields += [
				f"`{df.fieldname}`= REPLACE(REPLACE(`{df.fieldname}`, %(name)s,"
				f" 'REDACTED'), %(email)s, '{self.anon}')",
			]

		update_predicate = f"SET  {', '.join(match_fields)}"
		where_predicate = (
			"" if pagetype.get("strict") else f"WHERE `{pagetype.get('filter_by', 'owner')}` = %(email)s"
		)

		jingrow.db.sql(
			f"UPDATE `tab{pagetype['pagetype']}` {update_predicate} {where_predicate}",
			{"name": self.full_name, "email": self.email},
		)

	@jingrow.whitelist()
	def put_on_hold(self):
		self.db_set("status", "On Hold")


def process_data_deletion_request():
	auto_account_deletion = jingrow.db.get_single_value("Website Settings", "auto_account_deletion")
	if auto_account_deletion < 1:
		return

	requests = jingrow.get_all(
		"Personal Data Deletion Request", filters={"status": "Pending Approval"}, pluck="name"
	)

	for request in requests:
		pg = jingrow.get_pg("Personal Data Deletion Request", request)
		if time_diff_in_hours(get_datetime(), pg.creation) >= auto_account_deletion:
			pg.add_comment(
				"Comment",
				_(
					"The User record for this request has been auto-deleted due to inactivity by system admins."
				),
			)
			pg.trigger_data_deletion()


def remove_unverified_record():
	jingrow.db.sql(
		"""
		DELETE FROM `tabPersonal Data Deletion Request`
		WHERE `status` = 'Pending Verification'
		AND `creation` < (NOW() - INTERVAL '7' DAY)"""
	)


@jingrow.whitelist(allow_guest=True)
def confirm_deletion(email, name, host_name):
	if not verify_request():
		return

	pg = jingrow.get_pg("Personal Data Deletion Request", name)
	host_name = jingrow.utils.get_url()

	if pg.status == "Pending Verification":
		pg.status = "Pending Approval"
		pg.save(ignore_permissions=True)
		pg.notify_system_managers()
		jingrow.db.commit()
		jingrow.respond_as_web_page(
			_("Confirmed"),
			_("The process for deletion of {0} data associated with {1} has been initiated.").format(
				host_name, email
			),
			indicator_color="green",
		)

	else:
		jingrow.respond_as_web_page(
			_("Link Expired"),
			_("This link has already been activated for verification."),
			indicator_color="red",
		)


def get_pattern(full_match):
	return re.compile(rf"(?<!\.)\b{re.escape(full_match)}\b(?!\.)")
