"""
The two emails `users` sends: confirm your address, and reset your password.

ONE TEMPLATE, TWO CONTEXTS. They are the same email — a sentence, a button and
an expiry — and rendering them from one file is what keeps the second one from
drifting into a different-looking message from the same product. Everything
that differs is in `_CONTEXT` below, which is also the whole list of what a
third one would need.

Sending follows the pattern `waitlist/views.py` established: a daemon thread
per message, a `_safe` wrapper that logs instead of raising. There is no Celery
on a one-core box, and a registration that 500s because an SMTP handshake was
slow is worse than an email that arrives late.
"""

import logging
import threading
import urllib.parse

from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string

from .models import EmailToken

logger = logging.getLogger(__name__)

_CONTEXT = {
    EmailToken.PURPOSE_VERIFY: {
        'subject': 'Confirm your email for Lifey',
        'preview': 'One press and your Lifey account is ready.',
        'heading': 'Confirm your email address',
        'lead': 'You created a Lifey account with this address. Press the button to confirm it, '
                'and your account is ready to activate.',
        'button_label': 'Confirm my email',
        'expiry': 'This link works for 24 hours.',
        'ignore_note': 'If you did not create a Lifey account, ignore this email. '
                       'Nothing was activated and the address was not added to any list.',
        'fragment': 'verify',
    },
    EmailToken.PURPOSE_RESET: {
        'subject': 'Reset your Lifey password',
        'preview': 'A link to set a new password, good for one hour.',
        'heading': 'Set a new password',
        'lead': 'Somebody asked to reset the password on the Lifey account with this address. '
                'Press the button to choose a new one.',
        'button_label': 'Choose a new password',
        'expiry': 'This link works for one hour, and only once.',
        'ignore_note': 'If this was not you, ignore this email. Your password has not changed, '
                       'and the link expires on its own.',
        'fragment': 'reset',
    },
}


def action_url(purpose, token):
    """
    Where the button points.

    A FRAGMENT, NOT A QUERY STRING, for the reason the OAuth callback uses one:
    a fragment is never sent to a server, so the token cannot reach the web
    server's access log or the `Referer` of anything the page loads next.
    """
    context = _CONTEXT[purpose]
    return f'{settings.LIFEY_WEB_AUTH_URL}#{context["fragment"]}={urllib.parse.quote(token)}'


def send_action_email(user, purpose, token):
    context = _CONTEXT[purpose]

    body = render_to_string('Emails/users/action-link.html', {
        **context,
        'action_url': action_url(purpose, token),
        'email': user.email,
    })

    send_mail(
        subject=context['subject'],
        message='',
        html_message=body,
        from_email=settings.DEFAULT_FROM_EMAIL,
        recipient_list=[user.email],
        fail_silently=False,
    )


def _send_safe(user, purpose, token):
    try:
        send_action_email(user, purpose, token)
    except Exception:
        # The address is not logged. It is the thing the token unlocks, and a
        # log line is the one copy of it nobody is watching.
        logger.exception('Failed to send %s email (user %s)', purpose, user.pk)


def send_in_background(user, purpose, token):
    """
    Post the email on a daemon thread, so an SMTP handshake cannot slow down a
    registration or a reset request.

    `LIFEY_EMAIL_SYNC` sends on the calling thread instead. It exists for the
    tests, which read `mail.outbox` right after the request and would
    otherwise be racing a thread that may not have run yet. It is NOT a
    feature flag: turning it on in production puts the SMTP round trip back
    inside the request.
    """
    if getattr(settings, 'LIFEY_EMAIL_SYNC', False):
        _send_safe(user, purpose, token)
        return

    threading.Thread(target=_send_safe, args=(user, purpose, token), daemon=True).start()
