import logging

from django.conf import settings
from django.db.models import Case, IntegerField, Q, Value, When
from django.template.loader import render_to_string

from notifier.models import Notifier, NotifierRoute
from notifier.registry import get_notifier_backend

logger = logging.getLogger(__name__)


def render_notification_content(*, notifier: Notifier, payload: dict) -> str:
    context = {
        **(payload or {}),
        "notifier_config": notifier.config or {},
        "notifier_config_url": notifier.get_config_value("url", "") or "",
        "notifier_config_image_url": notifier.get_config_value("image_url", "") or "",
        "default_news_image_url": getattr(settings, "MESSAGE_API_DEFAULT_NEWS_IMAGE_URL", "") or "",
        "notifier": notifier,
    }
    content = render_to_string(notifier.get_template_name(), context).strip()
    if not content:
        raise ValueError("通知模板渲染结果不能为空")
    return content


def send_notification_with_notifier(*, notifier: Notifier, payload: dict) -> dict:
    content = render_notification_content(notifier=notifier, payload=payload)
    backend = get_notifier_backend(notifier.channel)
    backend_result = backend.notify(notifier=notifier, content=content, context=payload)
    result = {
        "notifier_id": notifier.id,
        "notifier_name": notifier.name,
        "channel": notifier.channel,
        "template_key": notifier.template_key,
        "status": "sent",
        **backend_result,
    }
    logger.info("[notifier.services] notification sent: %s", result)
    return result


def _match_notifier_routes(*, event_key: str, merchant_id: int, payload: dict | None = None) -> list[NotifierRoute]:
    payload = payload or {}
    category_id = payload.get("category_id")
    queryset = NotifierRoute.objects.filter(
        merchant_id=merchant_id,
        event_key=event_key,
        is_enabled=True,
        notifier__is_enabled=True,
    ).select_related("notifier", "mission_category")

    if category_id is not None:
        queryset = queryset.filter(Q(mission_category_id=category_id) | Q(mission_category__isnull=True)).annotate(
            route_priority=Case(
                When(mission_category_id=category_id, then=Value(0)),
                default=Value(1),
                output_field=IntegerField(),
            )
        )
    else:
        queryset = queryset.filter(mission_category__isnull=True).annotate(
            route_priority=Value(0, output_field=IntegerField())
        )

    queryset = queryset.order_by("notifier_id", "route_priority", "id")
    matched_by_notifier_id = {}
    for route in queryset:
        matched_by_notifier_id.setdefault(route.notifier_id, route)
    return list(matched_by_notifier_id.values())


def dispatch_notification_event(*, event_key: str, merchant_id: int, payload: dict | None = None) -> list[dict]:
    payload = payload or {}
    routes = _match_notifier_routes(
        event_key=event_key,
        merchant_id=merchant_id,
        payload=payload,
    )
    if not routes:
        logger.info(
            "[notifier.services] no enabled notifier route matched: event_key=%s merchant_id=%s category_id=%s",
            event_key,
            merchant_id,
            payload.get("category_id"),
        )
        return []

    results = []
    for route in routes:
        notifier = route.notifier
        try:
            item = send_notification_with_notifier(notifier=notifier, payload=payload)
            item.update(
                {
                    "event_key": event_key,
                    "route_id": route.id,
                    "route_event_key": route.event_key,
                    "route_mission_category_id": route.mission_category_id,
                }
            )
            results.append(item)
            logger.info(
                "[notifier.services] notification routed: route_id=%s notifier_id=%s event_key=%s merchant_id=%s route_category_id=%s",
                route.id,
                notifier.id,
                event_key,
                merchant_id,
                route.mission_category_id,
            )
        except Exception as exc:
            logger.exception(
                "[notifier.services] notification failed: route_id=%s notifier_id=%s event_key=%s merchant_id=%s",
                route.id,
                notifier.id,
                event_key,
                merchant_id,
            )
            results.append(
                {
                    "notifier_id": notifier.id,
                    "notifier_name": notifier.name,
                    "event_key": event_key,
                    "channel": notifier.channel,
                    "template_key": notifier.template_key,
                    "route_id": route.id,
                    "route_event_key": route.event_key,
                    "route_mission_category_id": route.mission_category_id,
                    "status": "failed",
                    "error": str(exc),
                }
            )
    return results


def enqueue_notification_event(*, event_key: str, merchant_id: int, payload: dict | None = None) -> str | None:
    from notifier.tasks import dispatch_notification_event_task

    try:
        async_result = dispatch_notification_event_task.delay(
            event_key=event_key,
            merchant_id=merchant_id,
            payload=payload or {},
        )
    except Exception:
        logger.exception(
            "[notifier.services] queue notification task failed: event_key=%s merchant_id=%s",
            event_key,
            merchant_id,
        )
        return None

    logger.info(
        "[notifier.services] queued notification task: event_key=%s merchant_id=%s task_id=%s",
        event_key,
        merchant_id,
        async_result.id,
    )
    return async_result.id
