"""Centralised rewarded-advertisement event recording.

A single entry point, :func:`record_rewarded_event`, is called by the reward
routes so that analytics collection and security audit logging live in ONE place
instead of being sprinkled across handlers. For each event it:

  1. appends a row to the ``rewarded_ad_events`` analytics stream, and
  2. mirrors *security-significant* events into the existing admin audit trail
     (:func:`app.admin.audit.record_admin_action`) and the operational logger.

It never raises — analytics/audit must never break the reward flow.

Event vocabulary (analytics stream)
-----------------------------------
* ``impression``      — a rewarded ad was shown (the modal opened / a token minted)
* ``video_start``     — video playback began
* ``video_success``   — video played through successfully
* ``video_fallback``  — video failed to load but a sponsor image was shown instead
* ``video_error``     — video failed with no fallback (no reward granted)
* ``reward_granted``  — countdown completed and the reward was granted (completed view)
* ``reward_rejected`` — a claim was rejected (see ``reason``)

Rejection reasons: ``missing`` / ``expired`` / ``replay`` / ``tampered`` /
``wrong_user`` / ``inactive`` / ``rate_limit``.
"""

from __future__ import annotations

from flask import current_app

from app.extensions import db
from app.models import RewardedAdEvent

# --- analytics event types -------------------------------------------------
EVENT_IMPRESSION = "impression"
EVENT_VIDEO_START = "video_start"
EVENT_VIDEO_SUCCESS = "video_success"
EVENT_VIDEO_FALLBACK = "video_fallback"
EVENT_VIDEO_ERROR = "video_error"
EVENT_REWARD_GRANTED = "reward_granted"
EVENT_REWARD_REJECTED = "reward_rejected"

VALID_EVENTS = {
    EVENT_IMPRESSION, EVENT_VIDEO_START, EVENT_VIDEO_SUCCESS, EVENT_VIDEO_FALLBACK,
    EVENT_VIDEO_ERROR, EVENT_REWARD_GRANTED, EVENT_REWARD_REJECTED,
}
# Video events a client-side beacon is permitted to report.
CLIENT_EVENTS = {EVENT_VIDEO_START, EVENT_VIDEO_SUCCESS, EVENT_VIDEO_FALLBACK, EVENT_VIDEO_ERROR}

# --- rejection reasons -----------------------------------------------------
REASON_MISSING = "missing"
REASON_EXPIRED = "expired"
REASON_REPLAY = "replay"
REASON_TAMPERED = "tampered"
REASON_WRONG_USER = "wrong_user"
REASON_INACTIVE = "inactive"
REASON_RATE_LIMIT = "rate_limit"

# Security-significant events → admin audit trail action name (registered in
# app.admin.audit.ACTION_CATALOG under the "reward" category).
_AUDIT_ACTION = {
    EVENT_REWARD_GRANTED: "reward_claimed",
    EVENT_REWARD_REJECTED: "reward_rejected",
    EVENT_VIDEO_ERROR: "reward_video_failed",
}


def record_rewarded_event(event_type, *, user=None, ad=None, request=None, reason=None, audit=True):
    """Record one rewarded-ad event to analytics (+ the audit trail for security).

    ``ad`` is an :class:`~app.models.Advertisement` or ``None`` (the placeholder).
    ``user`` is the acting user (required for the audit fan-out). Never raises.
    """
    if event_type not in VALID_EVENTS:
        return

    # 1) analytics stream — one small indexed insert (negligible latency).
    try:
        db.session.add(RewardedAdEvent(
            advertisement_id=getattr(ad, "id", None),
            user_id=getattr(user, "id", None),
            event_type=event_type,
            reason=reason,
        ))
        db.session.commit()
    except Exception:  # analytics must never break the reward flow
        db.session.rollback()
        current_app.logger.exception("rewarded analytics: failed to record %s", event_type)

    # 2) security audit trail (curated subset) + operational log.
    if audit and user is not None and event_type in _AUDIT_ACTION:
        try:
            from app.admin.audit import record_admin_action  # lazy: avoid import cycle

            record_admin_action(
                user, _AUDIT_ACTION[event_type], ad,
                request=request, details={"reason": reason} if reason else None,
            )
        except Exception:
            current_app.logger.exception("rewarded audit: failed for %s", event_type)

    if event_type == EVENT_REWARD_REJECTED:
        current_app.logger.warning(
            "reward_rejected reason=%s user=%s ad=%s",
            reason, getattr(user, "id", None), getattr(ad, "public_id", None),
        )
