# ContentDB
# Copyright (C) 2018-21 rubenwardy
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
import smtplib
import typing
from typing import Dict

from flask import render_template, escape
from flask_babel import force_locale, gettext, lazy_gettext, LazyString
from flask_mail import Message
from app import mail
from app.models import Notification, db, EmailSubscription, User
from app.rediscache import increment_key
from app.tasks import celery
from app.utils import abs_url_for, abs_url, random_string


def get_email_subscription(email):
	assert type(email) == str
	ret = EmailSubscription.query.filter_by(email=email).first()
	if not ret:
		ret = EmailSubscription(email)
		ret.token = random_string(32)
		db.session.add(ret)
		db.session.commit()

	return ret


def gen_headers(sub: EmailSubscription, is_bulk: bool) -> Dict[str,str]:
	headers = {"List-Help": f"<{abs_url_for('flatpage', path='help/faq/')}>", "List-Unsubscribe": f"<{sub.url}>"}

	if is_bulk:
		headers["Precedence"] = "Bulk"

	return headers


@celery.task()
def send_verify_email(email, token, locale):
	sub = get_email_subscription(email)
	if sub.blacklisted:
		return

	with force_locale(locale or "en"):
		msg = Message("Confirm email address", recipients=[email], extra_headers=gen_headers(sub, False))

		msg.body = """
				This email has been sent to you because someone (hopefully you)
				has entered your email address as a user's email.

				If it wasn't you, then just delete this email.

				If this was you, then please click this link to confirm the address:

				{}
			""".format(abs_url_for('users.verify_email', token=token))

		msg.html = render_template("emails/verify.html", token=token, sub=sub)
		mail.send(msg)
		increment_key("emails_sent")


@celery.task()
def send_unsubscribe_verify(email, locale):
	sub = get_email_subscription(email)
	if sub.blacklisted:
		return

	with force_locale(locale or "en"):
		msg = Message("Confirm unsubscribe", recipients=[email], extra_headers=gen_headers(sub, False))

		msg.body = """
					We're sorry to see you go. You just need to do one more thing before your email is blacklisted.
					
					Click this link to blacklist email: {} 
				""".format(abs_url_for('users.unsubscribe', token=sub.token))

		msg.html = render_template("emails/verify_unsubscribe.html", sub=sub)
		mail.send(msg)
		increment_key("emails_sent")


@celery.task(rate_limit="25/m")
def send_email_with_reason(email: str, locale: str, subject: str, text: str, html: str,
		reason: typing.Union[str, LazyString], conn: any):
	sub = get_email_subscription(email)
	if sub.blacklisted:
		return

	with force_locale(locale or "en"):
		msg = Message(subject, recipients=[email], extra_headers=gen_headers(sub, conn is not None))

		msg.body = text
		html = html or f"<pre>{escape(text)}</pre>"
		msg.html = render_template("emails/base.html", subject=subject, content=html, reason=reason, sub=sub)
		if conn:
			conn.send(msg)
		else:
			mail.send(msg)
		increment_key("emails_sent")


@celery.task(rate_limit="25/m")
def send_user_email(email: str, locale: str, subject: str, text: str, html=None, conn=None):
	return send_email_with_reason(email, locale, subject, text, html,
			lazy_gettext("You are receiving this email because you are a registered user of ContentDB."), conn)


@celery.task(rate_limit="25/m")
def send_anon_email(email: str, locale: str, subject: str, text: str, html=None):
	return send_email_with_reason(email, locale, subject, text, html,
			lazy_gettext("You are receiving this email because someone (hopefully you) entered your email address as a user's email."), None)


def send_single_email(notification, locale):
	sub = get_email_subscription(notification.user.email)
	if sub.blacklisted:
		return

	with force_locale(locale or "en"):
		msg = Message(notification.title, recipients=[notification.user.email], extra_headers=gen_headers(sub, False))

		msg.body = """
				New notification: {}
				
				View: {}
				
				Manage email settings: {}
				Unsubscribe: {}
			""".format(notification.title, abs_url(notification.url),
						abs_url_for("users.email_notifications", username=notification.user.username),
						abs_url_for("users.unsubscribe", token=sub.token))

		msg.html = render_template("emails/notification.html", notification=notification, sub=sub)
		mail.send(msg)
		increment_key("emails_sent")


def send_notification_digest(notifications: [Notification], locale):
	user = notifications[0].user

	sub = get_email_subscription(user.email)
	if sub.blacklisted:
		return

	with force_locale(locale or "en"):
		msg = Message(gettext("%(num)d new notifications", num=len(notifications)), recipients=[user.email])

		msg.body = "".join(["<{}> {}\n{}: {}\n\n".format(notification.causer.display_name, notification.title, gettext("View"), abs_url(notification.url)) for notification in notifications])

		msg.body += "{}: {}\n{}: {}".format(
				gettext("Manage email settings"),
				abs_url_for("users.email_notifications", username=user.username),
				gettext("Unsubscribe"),
				abs_url_for("users.unsubscribe", token=sub.token))

		msg.html = render_template("emails/notification_digest.html", notifications=notifications, user=user, sub=sub)
		mail.send(msg)
		increment_key("emails_sent")


@celery.task()
def send_pending_digests():
	for user in User.query.filter(User.notifications.any(emailed=False)).all():
		to_send = []
		for notification in user.notifications:
			if not notification.emailed and notification.can_send_digest():
				to_send.append(notification)
				notification.emailed = True

		if len(to_send) > 0:
			send_notification_digest(to_send, user.locale or "en")

		db.session.commit()


@celery.task()
def send_pending_notifications():
	for user in User.query.filter(User.notifications.any(emailed=False)).all():
		to_send = []
		for notification in user.notifications:
			if not notification.emailed:
				if notification.can_send_email():
					to_send.append(notification)
					notification.emailed = True
				elif not notification.can_send_digest():
					notification.emailed = True

		db.session.commit()

		try:
			if len(to_send) > 1:
				send_notification_digest(to_send, user.locale or "en")
			elif len(to_send) > 0:
				send_single_email(to_send[0], user.locale or "en")
		except smtplib.SMTPDataError as e:
			if "Message rejected under suspicion of SPAM" in str(e):
				raise Exception(f"Failed to send email to {user.username} due to yandex spam filter") from e
			else:
				raise e

@celery.task()
def send_bulk_email(subject: str, text: str, html=None):
	with mail.connect() as conn:
		for user in User.query.filter(User.email.isnot(None)).all():
			send_user_email(user.email, user.locale or "en", subject, text, html, conn)