"""
KonstructMessage — the unified internal message format.

All channel adapters (Slack, WhatsApp, Mattermost, etc.) normalize inbound
events into this format before passing them to the Message Router. The core
business logic never depends on which messaging platform a message came from.
"""

from __future__ import annotations

import uuid
from datetime import datetime
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, Field


class ChannelType(StrEnum):
    """Supported messaging channels."""

    SLACK = "slack"
    WHATSAPP = "whatsapp"
    MATTERMOST = "mattermost"
    ROCKETCHAT = "rocketchat"
    TEAMS = "teams"
    TELEGRAM = "telegram"
    SIGNAL = "signal"
    WEB = "web"


class MediaType(StrEnum):
    """Supported media attachment types."""

    IMAGE = "image"
    DOCUMENT = "document"
    AUDIO = "audio"
    VIDEO = "video"


class MediaAttachment(BaseModel):
    """
    A media file attached to a message (image, document, audio, or video).

    After normalization, `url` contains a placeholder media ID URL from the
    channel's API. The channel adapter downloads the media and stores it in
    MinIO, then updates `storage_key` and `url` with the final presigned URL.
    """

    media_type: MediaType = Field(description="Type of media: image, document, audio, or video")
    url: str | None = Field(
        default=None,
        description="Download URL — placeholder media ID URL after normalization, presigned MinIO URL after storage",
    )
    storage_key: str | None = Field(
        default=None,
        description="MinIO object key: {tenant_id}/{agent_id}/{message_id}/{filename}",
    )
    mime_type: str | None = Field(default=None, description="MIME type (e.g. image/jpeg)")
    filename: str | None = Field(default=None, description="Original filename if available")
    size_bytes: int | None = Field(default=None, description="File size in bytes if available")


class SenderInfo(BaseModel):
    """Information about the message sender."""

    user_id: str = Field(description="Channel-native user ID (e.g. Slack user ID U12345)")
    display_name: str = Field(description="Human-readable display name")
    email: str | None = Field(default=None, description="Sender email if available")
    is_bot: bool = Field(default=False, description="True if sender is a bot/automation")


class MessageContent(BaseModel):
    """The content of a message — text and optional attachments."""

    text: str = Field(description="Plain text content of the message")
    html: str | None = Field(default=None, description="HTML-formatted content if available")
    attachments: list[dict[str, Any]] = Field(
        default_factory=list,
        description="File attachments, images, or structured payloads",
    )
    mentions: list[str] = Field(
        default_factory=list,
        description="List of user/bot IDs mentioned in the message",
    )
    media: list[MediaAttachment] = Field(
        default_factory=list,
        description="Typed media attachments (images, documents, audio, video)",
    )


class KonstructMessage(BaseModel):
    """
    Unified internal message format for Konstruct.

    All channel adapters normalize events into this format. Downstream services
    (Router, Orchestrator) operate exclusively on KonstructMessage — they never
    inspect channel-specific fields directly.

    `tenant_id` is None immediately after normalization. The Message Router
    populates it via channel_connections lookup before forwarding.
    """

    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Unique message ID (UUID)",
    )
    tenant_id: str | None = Field(
        default=None,
        description="Konstruct tenant ID — populated by Message Router after resolution",
    )
    channel: ChannelType = Field(description="Source messaging channel")
    channel_metadata: dict[str, Any] = Field(
        description="Channel-specific identifiers: workspace_id, channel_id, bot_user_id, etc."
    )
    sender: SenderInfo = Field(description="Message sender information")
    content: MessageContent = Field(description="Message content")
    timestamp: datetime = Field(description="Message timestamp (UTC)")
    thread_id: str | None = Field(
        default=None,
        description="Thread identifier for threaded conversations (e.g. Slack thread_ts)",
    )
    reply_to: str | None = Field(
        default=None,
        description="Parent message ID if this is a reply",
    )
    context: dict[str, Any] = Field(
        default_factory=dict,
        description="Extracted intent, entities, sentiment — populated by downstream processors",
    )
