"""
Internal signal handlers wired up by AaSkipEmailConfig.ready().

The detection of a placeholder->real transition is done implicitly via
``pre_save`` + ``post_save`` on the ``User`` model: ``pre_save`` reads
the current DB value for ``email`` and stashes it on the instance,
``post_save`` compares it to the now-saved value and dispatches
``placeholder_email_replaced`` when the transition matches.

Cost: one extra ``SELECT email`` per ``user.save()`` whose
``update_fields`` includes ``email`` (or whose ``update_fields`` is
None — i.e. full save). This is acceptable because:

- Email writes are infrequent in practice.
- The plugin's own backfill loop bypasses the extra fetch by skipping
  the ``post_save`` emit when ``old`` and ``new`` are both
  placeholders, which is the only case it produces.
"""

from typing import Any

from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractBaseUser
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver

from aa_skip_email.helpers import is_placeholder_email
from aa_skip_email.signals import placeholder_email_replaced

User = get_user_model()

# Stashed on the instance between pre_save and post_save. The leading
# underscore + plugin namespace make collisions with other signal
# handlers unlikely.
_OLD_EMAIL_ATTR = "_aa_skip_email_old_email"


@receiver(
    pre_save, sender=User, dispatch_uid="aa_skip_email.capture_old_email"
)
def _capture_old_email(  # pyright: ignore[reportUnusedFunction]
    sender: type[AbstractBaseUser],
    instance: AbstractBaseUser,
    raw: bool,
    update_fields: frozenset[str] | None,
    **kwargs: Any,
) -> None:
    """Stash the pre-save email so post_save can compare."""
    if raw or instance.pk is None:
        # raw=True: fixture loading, skip side effects.
        # No pk: brand-new user; nothing to compare against.
        return
    if update_fields is not None and "email" not in update_fields:
        # Save touches other fields only — email cannot change.
        return
    try:
        old_email = sender.objects.only("email").get(pk=instance.pk).email
    except sender.DoesNotExist:
        # Race: the user was deleted between pre_save and pk read.
        return
    setattr(instance, _OLD_EMAIL_ATTR, old_email)


@receiver(
    post_save,
    sender=User,
    dispatch_uid="aa_skip_email.emit_placeholder_replaced",
)
def _emit_placeholder_replaced(  # pyright: ignore[reportUnusedFunction]
    sender: type[AbstractBaseUser],
    instance: AbstractBaseUser,
    created: bool,
    raw: bool,
    **kwargs: Any,
) -> None:
    """Dispatch ``placeholder_email_replaced`` on placeholder -> real."""
    if raw or created:
        return
    old_email = getattr(instance, _OLD_EMAIL_ATTR, None)
    if old_email is None:
        # pre_save did not run (different signal source) or was skipped
        # because update_fields excluded `email`. Nothing to emit.
        return
    # Clear the marker so a subsequent save without an email change does
    # not re-emit on stale state.
    delattr(instance, _OLD_EMAIL_ATTR)

    new_email = instance.email
    if old_email == new_email:
        return
    if is_placeholder_email(old_email) and not is_placeholder_email(new_email):
        placeholder_email_replaced.send(
            sender=sender,
            user=instance,
            old_email=old_email,
            new_email=new_email,
        )
