from hashlib import blake2b
import logging
import re
from typing import Any

from django.core.cache import cache
from django.utils.regex_helper import _lazy_re_compile as re_compile
from django.utils.log import AdminEmailHandler
from console_base.typehints import StrOrPromise

logger = logging.getLogger(__name__)

DETAIL_URL_REGEX = re_compile(
    r'^(.*)\/(\d+|[a-f0-9]{8}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12})\/$',
    re.IGNORECASE,
)


class ThrottledAdminEmailHandler(AdminEmailHandler):
    """
    Errors can occur rapidly - in the example that prompted this class,
    an update to uvicorn changed how the client IP was added to `request.META`
    which generated many thousands of errors *per machine* when sync ran.
    The mail server crashed and appliance hard drives started filling up.

    So throttle such messages, to prevent an error reporting service from
    bringing down the entire ecosystem.
    """

    THROTTLE_DURATION = 120

    def send_mail(
        self,
        subject: StrOrPromise,
        message: StrOrPromise,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        if self.deliver_report_email(subject):
            return super().send_mail(subject, message, *args, **kwargs)

    def deliver_report_email(self, subject: StrOrPromise) -> bool:
        """
        Rate-limit error messages with the same subject line.
        """
        # For detail URLs, strip off the integer / UUID primary key
        # [DJANGO] Internal Error /accounts/company/detail/1/
        # [DJANGO] Internal Server Error: /accounts/company/detail/1/
        # Error on /categories/consolecategory/detail/1e8522f7-daaa-672e-ba1c-4439c46d4bd4/
        if detail_url := DETAIL_URL_REGEX.search(str(subject)):
            subject = detail_url.group(1)

        # deterministically hash value, so it's consistent across python processes
        cache_key = blake2b(str(subject).encode('utf8'), digest_size=16).hexdigest()

        try:
            error_total = cache.incr(cache_key)
        except ValueError:
            error_total = 1
            cache.set(cache_key, error_total, self.THROTTLE_DURATION)

        skip_error_email = error_total and error_total > 1

        if skip_error_email:
            logger.info(
                '%s error occurred %s times in %s seconds - skip emailing error email.',
                subject,
                error_total,
                self.THROTTLE_DURATION,
            )

        return not skip_error_email
