"""Purge the frontend cache when content changes.

The frontend caches all GraphQL responses under a single "cms" cache tag;
any content change POSTs to its /api/revalidate endpoint for a global purge.
"""

import logging
import threading
import urllib.request

from django.conf import settings
from django.db import transaction
from django.db.models.signals import post_delete, post_save

logger = logging.getLogger(__name__)

WEBHOOK_TIMEOUT_SECONDS = 5

# Non-page models exposed over GraphQL; pages are covered by wagtail signals
REVALIDATING_MODELS = [
    "contacts.ContactEntity",
    "events.EventCategory",
    "events.EventOrganizer",
    "images.CustomImage",
    "openinghours.OpeningHoursSet",
]

_warned_unconfigured = False


def _send_webhook(url, secret, reason):
    request = urllib.request.Request(
        url, data=b"", method="POST", headers={"X-Revalidate-Secret": secret}
    )
    try:
        with urllib.request.urlopen(request, timeout=WEBHOOK_TIMEOUT_SECONDS) as response:
            logger.info("frontend revalidation ok (%s): HTTP %s", reason, response.status)
    except Exception:
        logger.exception("frontend revalidation failed (%s)", reason)


def trigger_frontend_revalidation(reason):
    global _warned_unconfigured
    secret = settings.REVALIDATE_WEBHOOK_SECRET
    if not secret:
        if not _warned_unconfigured:
            logger.warning("REVALIDATE_WEBHOOK_SECRET is not set, skipping frontend revalidation")
            _warned_unconfigured = True
        return
    url = f"{settings.FRONTEND_BASE_URL}/api/revalidate"
    # After commit so the frontend can't re-render from pre-commit data,
    # in a thread so publishing never blocks on the frontend
    transaction.on_commit(
        lambda: threading.Thread(
            target=_send_webhook, args=(url, secret, reason), daemon=True
        ).start()
    )


def _on_page_published(sender, instance, **kwargs):
    trigger_frontend_revalidation(f"page_published: {instance.slug}")


def _on_page_unpublished(sender, instance, **kwargs):
    trigger_frontend_revalidation(f"page_unpublished: {instance.slug}")


def _on_page_moved(sender, instance, **kwargs):
    trigger_frontend_revalidation(f"post_page_move: {instance.slug}")


def _on_page_deleted(sender, instance, **kwargs):
    trigger_frontend_revalidation(f"page_deleted: {instance.slug}")


def _on_model_changed(sender, instance, **kwargs):
    trigger_frontend_revalidation(f"{sender._meta.label}: {instance}")


def register_signal_handlers():
    from wagtail.models import Page
    from wagtail.signals import page_published, page_unpublished, post_page_move

    page_published.connect(_on_page_published, dispatch_uid="revalidation.page_published")
    page_unpublished.connect(_on_page_unpublished, dispatch_uid="revalidation.page_unpublished")
    post_page_move.connect(_on_page_moved, dispatch_uid="revalidation.post_page_move")
    # Wagtail sends no signal on page deletion; MTI deletes of any page type
    # also emit post_delete for the base Page row
    post_delete.connect(_on_page_deleted, sender=Page, dispatch_uid="revalidation.page_deleted")

    for label in REVALIDATING_MODELS:
        post_save.connect(
            _on_model_changed, sender=label, dispatch_uid=f"revalidation.save.{label}"
        )
        post_delete.connect(
            _on_model_changed, sender=label, dispatch_uid=f"revalidation.delete.{label}"
        )
