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

import json
from collections.abc import Iterable
from typing import TYPE_CHECKING

import jingrow
import jingrow.email.smtp
from jingrow import _
from jingrow.email.email_body import get_message_id
from jingrow.utils import (
	cint,
	get_datetime,
	get_formatted_email,
	get_imaginary_pixel_response,
	get_string_between,
	list_to_str,
	split_emails,
	validate_email_address,
)

if TYPE_CHECKING:
	from jingrow.core.pagetype.email.email import Email


@jingrow.whitelist()
def make(
	pagetype=None,
	name=None,
	content=None,
	subject=None,
	sent_or_received="Sent",
	sender=None,
	sender_full_name=None,
	recipients=None,
	send_email=False,
	print_html=None,
	print_format=None,
	attachments=None,
	send_me_a_copy=False,
	cc=None,
	bcc=None,
	read_receipt=None,
	print_letterhead=True,
	email_template=None,
	send_after=None,
	print_language=None,
	now=False,
	email_status=None,
	**kwargs,
) -> dict[str, str]:
	"""Make a new email. Checks for email permissions for specified Page.

	:param pagetype: Reference PageType.
	:param name: Reference Page name.
	:param content: Email body.
	:param subject: Email subject.
	:param sent_or_received: Sent or Received (default **Sent**).
	:param sender: Email sender (default current user).
	:param recipients: Email recipients as list.
	:param send_email: Send via email (default **False**).
	:param print_html: HTML Print format to be sent as attachment.
	:param print_format: Print Format name of parent document to be sent as attachment.
	:param attachments: List of File names or dicts with keys "fname" and "fcontent"
	:param send_me_a_copy: Send a copy to the sender (default **False**).
	:param email_template: Template which is used to compose mail .
	:param send_after: Send after the given datetime.
	:param email_status: Email status (e.g. "Draft").
	"""
	if kwargs:
		from jingrow.utils.commands import warn

		warn(
			f"Options {kwargs} used in jingrow.core.pagetype.email.email_helpers.make "
			"are deprecated or unsupported",
			category=DeprecationWarning,
		)

	if pagetype and name:
		jingrow.has_permission(pagetype, pg=name, ptype="email", throw=True)

	return _make(
		pagetype=pagetype,
		name=name,
		content=content,
		subject=subject,
		sent_or_received=sent_or_received,
		sender=sender,
		sender_full_name=sender_full_name,
		recipients=recipients,
		send_email=send_email,
		print_html=print_html,
		print_format=print_format,
		attachments=attachments,
		send_me_a_copy=cint(send_me_a_copy),
		cc=cc,
		bcc=bcc,
		read_receipt=cint(read_receipt),
		print_letterhead=print_letterhead,
		email_template=email_template,
		add_signature=False,
		send_after=send_after,
		print_language=print_language,
		now=now,
		email_status=email_status,
	)


def _make(
	pagetype=None,
	name=None,
	content=None,
	subject=None,
	sent_or_received="Sent",
	sender=None,
	sender_full_name=None,
	recipients=None,
	send_email=False,
	print_html=None,
	print_format=None,
	attachments=None,
	send_me_a_copy=False,
	cc=None,
	bcc=None,
	read_receipt=None,
	print_letterhead=True,
	email_template=None,
	add_signature=True,
	send_after=None,
	print_language=None,
	now=False,
	email_status=None,
) -> dict[str, str]:
	"""Internal method to make a new Email that ignores Permission checks."""

	sender = sender or get_formatted_email(jingrow.session.user)
	recipients = list_to_str(recipients) if isinstance(recipients, list) else recipients
	cc = list_to_str(cc) if isinstance(cc, list) else cc
	bcc = list_to_str(bcc) if isinstance(bcc, list) else bcc

	email: "Email" = jingrow.get_pg(
		{
			"pagetype": "Email",
			"subject": subject,
			"content": content,
			"sender": sender,
			"sender_full_name": sender_full_name,
			"recipients": recipients,
			"cc": cc or None,
			"bcc": bcc or None,
			"sent_or_received": sent_or_received,
			"reference_pagetype": pagetype,
			"reference_name": name,
			"email_template": email_template,
			"message_id": get_string_between("<", get_message_id(), ">"),
			"read_receipt": read_receipt,
			"has_attachment": 1 if attachments else 0,
			"send_after": send_after,
			"email_status": email_status or "Open",
		}
	)
	email.flags.skip_add_signature = not add_signature
	email.insert(ignore_permissions=True)

	# if not committed, delayed task doesn't find the email
	if attachments:
		if isinstance(attachments, str):
			attachments = json.loads(attachments)
		add_attachments(email.name, attachments)

	if cint(send_email):
		if not email.get_outgoing_email_account():
			jingrow.throw(
				_(
					"Unable to send mail because of a missing email account. Please setup default Email Account from Settings > Email Account"
				),
				exc=jingrow.OutgoingEmailError,
			)

		email.send_email(
			print_html=print_html,
			print_format=print_format,
			send_me_a_copy=send_me_a_copy,
			print_letterhead=print_letterhead,
			print_language=print_language,
			now=now,
		)

	emails_not_sent_to = email.exclude_emails_list(include_sender=send_me_a_copy)

	return {"name": email.name, "emails_not_sent_to": ", ".join(emails_not_sent_to)}


def validate_email(pg: "Email") -> None:
	"""Validate Email Addresses of Recipients and CC"""
	if pg.flags.in_receive:
		return

	# validate recipients
	for email in split_emails(pg.recipients):
		validate_email_address(email, throw=True)

	# validate CC
	for email in split_emails(pg.cc):
		validate_email_address(email, throw=True)

	for email in split_emails(pg.bcc):
		validate_email_address(email, throw=True)


def set_incoming_outgoing_accounts(pg):
	from jingrow.email.pagetype.email_account.email_account import EmailAccount

	incoming_email_account = EmailAccount.find_incoming(
		match_by_email=pg.sender, match_by_pagetype=pg.reference_pagetype
	)
	pg.incoming_email_account = incoming_email_account.email_id if incoming_email_account else None

	pg.outgoing_email_account = EmailAccount.find_outgoing(
		match_by_email=pg.sender, match_by_pagetype=pg.reference_pagetype
	)

	if pg.sent_or_received == "Sent":
		pg.db_set("email_account", pg.outgoing_email_account.name)


def add_attachments(name: str, attachments: Iterable[str | dict]) -> None:
	"""Add attachments to the given Email

	:param name: Email name
	:param attachments: File names or dicts with keys "fname" and "fcontent"
	"""
	# loop through attachments
	for a in attachments:
		if isinstance(a, str):
			attach = jingrow.db.get_value("File", {"name": a}, ["file_url", "is_private"], as_dict=1)
			file_args = {
				"file_url": attach.file_url,
				"is_private": attach.is_private,
			}
		elif isinstance(a, dict) and "fcontent" in a and "fname" in a:
			# dict returned by jingrow.attach_print()
			file_args = {
				"file_name": a["fname"],
				"content": a["fcontent"],
				"is_private": 1,
			}
		else:
			continue

		file_args.update(
			{
				"attached_to_pagetype": "Email",
				"attached_to_name": name,
				"folder": "Home/Attachments",
			}
		)

		_file = jingrow.new_pg("File")
		_file.update(file_args)
		_file.save(ignore_permissions=True)


@jingrow.whitelist(allow_guest=True, methods=("GET",))
def mark_email_as_seen(name: str | None = None):
	jingrow.request.after_response.add(lambda: _mark_email_as_seen(name))
	jingrow.response.update(jingrow.utils.get_imaginary_pixel_response())


def _mark_email_as_seen(name):
	try:
		update_email_as_read(name)
	except Exception:
		jingrow.log_error("Unable to mark as seen", None, "Email", name)

	jingrow.db.commit()  # nosemgrep: after_response requires explicit commit


def update_email_as_read(name):
	if not name or not isinstance(name, str):
		return

	email = jingrow.db.get_value("Email", name, "read_by_recipient", as_dict=True)

	if not email or email.read_by_recipient:
		return

	jingrow.db.set_value(
		"Email",
		name,
		{"read_by_recipient": 1, "delivery_status": "Read", "read_by_recipient_on": get_datetime()},
	)
