"""Template context building — transforms ServiceEvent into template variables."""

from __future__ import annotations

import logging
from datetime import datetime
from typing import Any

from notify_bridge_core.models.events import ServiceEvent

_LOGGER = logging.getLogger(__name__)

# Per-target maximum video size (bytes).  None = no limit.
_MAX_VIDEO_SIZE_BY_TARGET: dict[str, int] = {
    "telegram": 50 * 1024 * 1024,  # 50 MB — Telegram Bot API hard limit
}

# Keys that must NEVER flow into the Jinja2 template context, even if a
# provider stuffs them into ``event.extra`` (webhooks, Immich metadata, etc.).
# Templates that could reach a Telegram/Discord/etc. chat would otherwise
# expose operator credentials if a template author simply did ``{{ api_key }}``.
# Case-insensitive substring match — any ``extra`` key containing one of these
# tokens is dropped before the merge.
_SENSITIVE_EXTRA_TOKENS: tuple[str, ...] = (
    "api_key",
    "apikey",
    "token",
    "secret",
    "password",
    "passwd",
    "hashed_",
    "authorization",
    "cookie",
    "session_id",
    "bearer",
    "private_key",
    "access_key",
    "oauth",
    "client_secret",
    "webhook_secret",
    "csrf",
)


def _is_sensitive_key(key: str) -> bool:
    lowered = str(key).lower()
    return any(tok in lowered for tok in _SENSITIVE_EXTRA_TOKENS)


def _safe_merge_extras(ctx: dict[str, Any], extras: dict[str, Any]) -> None:
    """Merge provider ``extras`` into ``ctx``, dropping sensitive keys.

    Dropped keys are logged once per event (DEBUG) so operators can spot
    leaking providers without flooding the log.
    """
    if not extras:
        return
    dropped: list[str] = []
    for key, value in extras.items():
        if _is_sensitive_key(key):
            dropped.append(key)
            continue
        ctx[key] = value
    if dropped:
        _LOGGER.debug(
            "Dropped %d sensitive key(s) from template context: %s",
            len(dropped), ", ".join(sorted(dropped)),
        )


def build_template_context(
    event: ServiceEvent,
    target_type: str = "webhook",
    date_format: str = "%d.%m.%Y, %H:%M UTC",
    date_only_format: str = "%d.%m.%Y",
) -> dict[str, Any]:
    """Build a flat template context dict from a ServiceEvent.

    Merges base variables with provider-specific extras.
    The context is passed directly to Jinja2 templates.

    Args:
        event: The service event to render.
        target_type: "telegram" or "webhook" — for conditional formatting.

    Returns:
        Dict of template variables.
    """
    # Base variables (available to all providers)
    ctx: dict[str, Any] = {
        "event_type": event.event_type.value,
        "timestamp": event.timestamp,
        "service_name": event.provider_name,
        "service_type": event.provider_type.value,
        "collection_name": event.collection_name,
        "collection_id": event.collection_id,
        "added_count": event.added_count,
        "removed_count": event.removed_count,
        "old_name": event.old_name,
        "new_name": event.new_name,
        "target_type": target_type,
    }

    # Convert MediaAsset list to dicts for template access
    assets = []
    for asset in event.added_assets:
        asset_dict: dict[str, Any] = {
            "id": asset.id,
            "type": asset.type.value.upper(),  # "IMAGE" or "VIDEO" for template compat
            "filename": asset.filename,
            "created_at": asset.created_at.isoformat() if asset.created_at else "",
            "owner": asset.owner_name or "",
            "description": asset.description or "",
            "tags": asset.tags,
            "thumbnail_url": asset.thumbnail_url or "",
            "preview_url": asset.preview_url or "",
            "full_url": asset.full_url or "",
        }
        # Flatten extras into asset dict for template access — same
        # sensitive-key filtering applied as the top-level merge.
        _safe_merge_extras(asset_dict, asset.extra)
        asset_dict.setdefault("oversized", False)
        asset_dict.setdefault("file_size", None)
        asset_dict.setdefault("playback_size", None)
        assets.append(asset_dict)

    # Enrich assets with per-asset public URLs if album has a public share link
    album_public_url = event.extra.get("public_url", "")
    if album_public_url:
        for asset_dict in assets:
            asset_dict["public_url"] = f"{album_public_url}/photos/{asset_dict['id']}"
    else:
        for asset_dict in assets:
            asset_dict.setdefault("public_url", "")

    ctx["assets"] = assets
    ctx["added_assets"] = assets  # alias for backward compat

    # Asset type flags
    ctx["has_videos"] = any(a.get("type") == "VIDEO" for a in assets)
    ctx["has_photos"] = any(a.get("type") == "IMAGE" for a in assets)

    # Per-target video size limit and oversized-video detection
    max_video_bytes = _MAX_VIDEO_SIZE_BY_TARGET.get(target_type)
    ctx["max_video_size"] = max_video_bytes  # bytes or None
    ctx["max_video_size_mb"] = max_video_bytes // (1024 * 1024) if max_video_bytes else None

    # Oversize check uses playback_size (bytes we actually upload). file_size
    # (original asset size) is informational only — for providers that transcode
    # before sending (e.g. Immich /video/playback), original can be much larger
    # than what reaches Telegram, so it would false-positive the warning.
    has_oversized = False
    if max_video_bytes:
        for a in assets:
            if a.get("type") == "VIDEO":
                size = a.get("playback_size")
                oversized = size is not None and size > max_video_bytes
                a["oversized"] = oversized
                if oversized:
                    has_oversized = True
    ctx["has_oversized_videos"] = has_oversized

    # Date format strings (available to templates for custom formatting)
    ctx["date_format"] = date_format
    ctx["date_only_format"] = date_only_format

    # Common date/location — set when ALL assets share the same value
    ctx["common_date"] = ""
    ctx["common_location"] = ""
    if len(assets) > 1:
        # Date: compare date portion only (YYYY-MM-DD)
        dates = set()
        for a in assets:
            ca = a.get("created_at", "")
            if ca:
                dates.add(ca[:10])  # "2026-03-19T..." -> "2026-03-19"
        if len(dates) == 1:
            raw_date = dates.pop()
            try:
                ctx["common_date"] = datetime.fromisoformat(raw_date).strftime(date_only_format)
            except (ValueError, TypeError):
                ctx["common_date"] = raw_date

        # Location: "City, Country" or just "City"
        locations = set()
        for a in assets:
            city = a.get("city", "")
            country = a.get("country", "")
            if city:
                loc = f"{city}, {country}" if country else city
                locations.add(loc)
            else:
                locations.add("")  # asset with no location breaks commonality
        if len(locations) == 1 and "" not in locations:
            ctx["common_location"] = locations.pop()

    # Provider-specific extras merged at top level. Sensitive keys (tokens,
    # secrets, auth headers) are dropped — see ``_SENSITIVE_EXTRA_TOKENS``.
    # Without this, a template author could exfiltrate provider credentials
    # via ``{{ api_key }}`` in an outgoing notification body.
    _safe_merge_extras(ctx, event.extra)

    # Ensure URL variables always exist (avoid Jinja2 undefined errors)
    ctx.setdefault("public_url", "")
    ctx.setdefault("protected_url", "")
    ctx.setdefault("album_url", "")

    # Provider-specific aliases for Immich
    if event.provider_type.value == "immich":
        ctx.setdefault("album_name", event.collection_name)
        ctx.setdefault("album_id", event.collection_id)
        if event.old_name:
            ctx.setdefault("old_album_name", event.old_name)
        if event.new_name:
            ctx.setdefault("new_album_name", event.new_name)

    # Provider-specific defaults for Generic Webhook
    if event.provider_type.value == "webhook":
        ctx.setdefault("raw_payload", event.extra.get("raw_payload", {}))
        ctx.setdefault("event_type_raw", event.extra.get("event_type_raw", "webhook_received"))
        ctx.setdefault("source_ip", event.extra.get("source_ip", ""))

    # Provider-specific defaults for Scheduler
    if event.provider_type.value == "scheduler":
        ctx.setdefault("schedule_name", event.collection_name)
        ctx.setdefault("fire_count", event.extra.get("fire_count", 0))
        ctx.setdefault("current_date", event.extra.get("current_date", ""))
        ctx.setdefault("current_time", event.extra.get("current_time", ""))
        ctx.setdefault("current_datetime", event.extra.get("current_datetime", ""))
        ctx.setdefault("weekday", event.extra.get("weekday", ""))
        ctx.setdefault("timezone", event.extra.get("timezone", "UTC"))
        ctx.setdefault("custom_vars", event.extra.get("custom_vars", {}))

    return ctx
