"""
Channel event normalization.

Converts channel-specific event payloads (Slack, WhatsApp, etc.) into the
unified KonstructMessage format.

All channel adapters produce KonstructMessage — the router and orchestrator
never inspect channel-specific fields directly.
"""

from __future__ import annotations

import re
import uuid
from datetime import datetime, timezone

from shared.models.message import (
    ChannelType,
    KonstructMessage,
    MediaAttachment,
    MediaType,
    MessageContent,
    SenderInfo,
)

# Pattern to strip <@BOT_USER_ID> mentions from message text.
# Slack injects <@U...> tokens for @mentions — we strip the bot mention
# so the agent sees clean user text, not the mention syntax.
_BOT_MENTION_RE = re.compile(r"<@[A-Z0-9]+>")


def normalize_slack_event(
    event: dict,
    workspace_id: str,
    bot_user_id: str = "",
) -> KonstructMessage:
    """
    Normalize a Slack Events API event payload into a KonstructMessage.

    Handles both ``app_mention`` events (where the bot is @mentioned in a
    channel) and ``message`` events in DMs (``channel_type == "im"``).

    The bot mention token (``<@BOT_USER_ID>``) is stripped from the beginning
    of the text for ``app_mention`` events so the agent receives clean input.

    Args:
        event:        The inner ``event`` dict from the Slack Events API payload.
        workspace_id: The Slack workspace ID (team_id from the outer payload).
        bot_user_id:  The bot's Slack user ID (used for mention stripping).

    Returns:
        A fully-populated KonstructMessage. ``tenant_id`` is ``None`` at this
        stage — the Message Router populates it via channel_connections lookup.
    """
    # Extract and clean user text
    raw_text: str = event.get("text", "") or ""
    # Strip any <@BOT_ID> mention tokens from the message
    clean_text = _BOT_MENTION_RE.sub("", raw_text).strip()

    # Slack thread_ts is the canonical thread identifier
    thread_ts: str | None = event.get("thread_ts") or event.get("ts")

    # Timestamp — Slack uses Unix float strings ("1234567890.123456")
    ts_raw = event.get("ts", "0")
    try:
        ts_float = float(ts_raw)
        timestamp = datetime.fromtimestamp(ts_float, tz=timezone.utc)
    except (ValueError, TypeError):
        timestamp = datetime.now(tz=timezone.utc)

    # User info — Slack provides user_id; display name is enriched later
    sender_user_id: str = event.get("user", "") or ""
    is_bot = bool(event.get("bot_id") or event.get("subtype") == "bot_message")

    # Build the set of mentions present in the original text
    mentions: list[str] = _BOT_MENTION_RE.findall(raw_text)
    # Strip angle brackets from extracted tokens: <@U123> -> U123
    mentions = [m.strip("<>@") for m in mentions]

    return KonstructMessage(
        id=str(uuid.uuid4()),
        tenant_id=None,  # Populated by Message Router
        channel=ChannelType.SLACK,
        channel_metadata={
            "workspace_id": workspace_id,
            "channel_id": event.get("channel", ""),
            "thread_ts": thread_ts,
            "bot_user_id": bot_user_id,
            "event_ts": ts_raw,
            "channel_type": event.get("channel_type", ""),
        },
        sender=SenderInfo(
            user_id=sender_user_id,
            display_name=sender_user_id,  # Enriched later if needed
            is_bot=is_bot,
        ),
        content=MessageContent(
            text=clean_text,
            mentions=mentions,
        ),
        timestamp=timestamp,
        thread_id=thread_ts,
    )


# ---------------------------------------------------------------------------
# WhatsApp normalization
# ---------------------------------------------------------------------------

# Map from WhatsApp media message type strings to MediaType enum
_WHATSAPP_MEDIA_TYPES: dict[str, MediaType] = {
    "image": MediaType.IMAGE,
    "document": MediaType.DOCUMENT,
    "audio": MediaType.AUDIO,
    "video": MediaType.VIDEO,
}


def normalize_whatsapp_event(payload: dict) -> KonstructMessage:
    """
    Normalize a Meta Cloud API v20.0 webhook payload into a KonstructMessage.

    Handles text, image, and document message types.

    For media messages (image/document), creates a MediaAttachment with a
    placeholder URL in the format ``meta-media://{media_id}``. The actual
    download from Meta's API and upload to MinIO happens in the WhatsApp
    channel adapter after normalization.

    WhatsApp has no threading concept — thread_id is set to the sender's
    wa_id so that conversation history is scoped per phone number.

    Args:
        payload: Parsed Meta Cloud API v20.0 webhook JSON (already-deserialized dict).

    Returns:
        A KonstructMessage. ``tenant_id`` is ``None`` at this stage — the
        Message Router populates it via channel_connections lookup.
    """
    # Meta Cloud API structure: entry[0].changes[0].value
    value: dict = payload["entry"][0]["changes"][0]["value"]
    metadata: dict = value.get("metadata", {})
    messages: list[dict] = value.get("messages", [])

    if not messages:
        # Status updates, read receipts, etc. — no messages to normalize
        raise ValueError("WhatsApp webhook payload contains no messages")

    message: dict = messages[0]
    msg_type: str = message.get("type", "text")
    wa_id: str = message.get("from", "")
    message_id: str = message.get("id", "")
    phone_number_id: str = metadata.get("phone_number_id", "")

    # Timestamp — Meta sends Unix integer timestamps as strings
    ts_raw: str = message.get("timestamp", "0")
    try:
        timestamp = datetime.fromtimestamp(int(ts_raw), tz=timezone.utc)
    except (ValueError, TypeError):
        timestamp = datetime.now(tz=timezone.utc)

    # Extract display name from contacts array
    contacts: list[dict] = value.get("contacts", [])
    display_name: str = wa_id
    if contacts:
        display_name = contacts[0].get("profile", {}).get("name", wa_id)

    # Extract content based on message type
    text_body: str = ""
    media_attachments: list[MediaAttachment] = []

    if msg_type == "text":
        text_body = message.get("text", {}).get("body", "")

    elif msg_type in _WHATSAPP_MEDIA_TYPES:
        media_data: dict = message.get(msg_type, {})
        media_id: str = media_data.get("id", "")
        attachment = MediaAttachment(
            media_type=_WHATSAPP_MEDIA_TYPES[msg_type],
            # Placeholder URL — actual download happens in adapter
            url=f"meta-media://{media_id}" if media_id else None,
            mime_type=media_data.get("mime_type"),
            filename=media_data.get("filename"),
        )
        media_attachments.append(attachment)
        # Caption is the text for media messages (may not be present)
        text_body = media_data.get("caption", "")

    return KonstructMessage(
        id=str(uuid.uuid4()),
        tenant_id=None,  # Populated by Message Router
        channel=ChannelType.WHATSAPP,
        channel_metadata={
            "phone_number_id": phone_number_id,
            "message_id": message_id,
            "wa_id": wa_id,
        },
        sender=SenderInfo(
            user_id=wa_id,
            display_name=display_name,
        ),
        content=MessageContent(
            text=text_body,
            media=media_attachments,
        ),
        timestamp=timestamp,
        # WhatsApp has no threading — use wa_id as conversation scope identifier
        thread_id=wa_id,
    )
