"""
Type definitions for Moveo.AI webhook requests and responses.

Drop this file into your project to validate the bodies your webhook
endpoint receives from Moveo and the responses you send back.

Requires: pydantic >= 2.0

Custom context variables: subclass MoveoContext and add your own fields,
or pass them as extras — MoveoContext allows extra keys by default.
"""

from __future__ import annotations

from typing import Any, Literal, Union

from pydantic import BaseModel, ConfigDict, Field

# ---------------------------------------------------------------------------
# Context
# ---------------------------------------------------------------------------


class MoveoUserLocation(BaseModel):
    city: str | None = None
    country: str | None = None
    region: str | None = None
    latitude: float | None = None
    longitude: float | None = None


class MoveoContextUser(BaseModel):
    """The `user` object inside `context`. Populated by Moveo from the channel
    the conversation is happening on and enriched with anything the agent
    collects during the conversation."""

    model_config = ConfigDict(extra="allow")

    user_id: str
    external_id: str | None = None

    display_name: str | None = None
    avatar: str | None = None
    email: str | None = None
    phone: str | None = None
    address: str | None = None

    language: str | None = None
    timezone: str | None = None
    location: MoveoUserLocation | None = None

    browser: str | None = None
    platform: str | None = None
    device: str | None = None
    ip: str | None = None

    verified: bool | None = None
    verified_method: Literal["jwt", "otp", "email", "qr"] | None = None
    verified_at: str | None = None

    past_visits: int | None = None
    past_chats: int | None = None
    last_seen_at: str | None = None
    last_replied_at: str | None = None


class MoveoCampaignContext(BaseModel):
    campaign_id: str | None = None
    subscriber_id: str | None = None
    sender_id: str | None = None


class MoveoContext(BaseModel):
    """The full session context. Extend this class with your own fields to
    type custom context variables your AI Agent collects.

    The `sys-*` fields are read only — Moveo rejects any attempt to overwrite
    them in the response.
    """

    model_config = ConfigDict(extra="allow", populate_by_name=True)

    user: MoveoContextUser | None = None
    tags: list[str] | None = None
    channels: dict[str, dict[str, Any]] | None = None
    global_: dict[str, Any] | None = Field(default=None, alias="global")
    campaign: MoveoCampaignContext | None = None
    live_instructions: str | dict[str, Any] | None = None

    sys_channel: str | None = Field(default=None, alias="sys-channel")
    sys_session: str | None = Field(default=None, alias="sys-session")
    sys_business: Literal["open", "closed"] | None = Field(
        default=None, alias="sys-business"
    )
    sys_user_message_counter: int | None = Field(
        default=None, alias="sys-user_message_counter"
    )
    sys_unknown_counter: int | None = Field(
        default=None, alias="sys-unknown_counter"
    )


# ---------------------------------------------------------------------------
# Common building blocks
# ---------------------------------------------------------------------------


class MoveoMessageAttachment(BaseModel):
    type: Literal["image", "video", "audio", "file"]
    url: str | None = None
    mime_type: str | None = None
    filename: str | None = None
    title: str | None = None


class MoveoMessageInput(BaseModel):
    text: str | None = None
    attachments: list[MoveoMessageAttachment] | None = None
    trigger_node_id: str | None = None


class MoveoIntent(BaseModel):
    intent: str
    confidence: float


class MoveoEntity(BaseModel):
    entity: str
    value: str
    start: int
    end: int
    confidence: float
    type: str


class MoveoHistoryMessage(BaseModel):
    role: Literal["user", "assistant"]
    content: str
    timestamp: int


class MoveoDialogStackEntry(BaseModel):
    node_id: str
    name: str


class MoveoDebug(BaseModel):
    dialog_stack: list[MoveoDialogStackEntry]


# ---------------------------------------------------------------------------
# Request bodies
# ---------------------------------------------------------------------------


class _MoveoWebhookRequestBase(BaseModel):
    """Fields present on every webhook request, regardless of type.

    `history` is only present when the webhook is configured with the
    `X-Moveo-Include-History: true` custom header.
    """

    model_config = ConfigDict(extra="allow")

    channel: str
    channel_type: str
    session_id: str
    desk_id: str
    integration_id: str
    brain_id: str
    lang: str
    context: MoveoContext
    timestamp: int
    history: list[MoveoHistoryMessage] | None = None


class DialogWebhookRequest(_MoveoWebhookRequestBase):
    """Body of a dialog webhook call (a webhook action inside a workflow node)."""

    input: MoveoMessageInput
    intents: list[MoveoIntent]
    entities: list[MoveoEntity]
    user_message_counter: int
    debug: MoveoDebug


class FirstMessageWebhookRequest(_MoveoWebhookRequestBase):
    """Body of a first message webhook call. Fires once per session for a
    given AI Agent on the user's first inbound message."""

    input: MoveoMessageInput
    business_closed: bool


class PreMessageWebhookRequest(_MoveoWebhookRequestBase):
    """Body of a pre message webhook call. Fires before every user message
    is processed by the agent."""

    input: MoveoMessageInput
    business_closed: bool


class PostMessageWebhookRequest(_MoveoWebhookRequestBase):
    """Body of a post message webhook call. Fires after the agent has
    generated its reply, before the reply is delivered to the user."""

    input: MoveoMessageInput
    intents: list[MoveoIntent]
    entities: list[MoveoEntity]
    user_message_counter: int
    output: list["MoveoAgentAction"]
    debug: MoveoDebug


# ---------------------------------------------------------------------------
# Response actions
# ---------------------------------------------------------------------------


class MoveoQuickReply(BaseModel):
    label: str
    text: str


class MoveoTextAction(BaseModel):
    type: Literal["text"] = "text"
    texts: list[str]
    options: list[MoveoQuickReply] | None = None


class MoveoImageAction(BaseModel):
    type: Literal["image"] = "image"
    url: str
    name: str | None = None
    size: int | None = None


class MoveoVideoAction(BaseModel):
    type: Literal["video"] = "video"
    url: str
    name: str | None = None
    size: int | None = None


class MoveoAudioAction(BaseModel):
    type: Literal["audio"] = "audio"
    url: str
    name: str | None = None
    size: int | None = None


class MoveoFileAction(BaseModel):
    type: Literal["file"] = "file"
    url: str
    name: str | None = None
    size: int | None = None


class MoveoEventAction(BaseModel):
    type: Literal["event"] = "event"
    trigger_node_id: str


class MoveoWebviewAction(BaseModel):
    type: Literal["webview"] = "webview"
    name: str
    label: str
    url: str
    height: Literal["tall", "compact", "full"] | None = None
    trigger_node_id: str | None = None


class MoveoResetAction(BaseModel):
    type: Literal["reset"] = "reset"


class MoveoCarouselMedia(BaseModel):
    type: Literal["image", "video"]
    url: str


class _MoveoCarouselButtonPostback(BaseModel):
    type: Literal["postback"] = "postback"
    label: str
    value: str


class _MoveoCarouselButtonURL(BaseModel):
    type: Literal["url"] = "url"
    label: str
    url: str


class _MoveoCarouselButtonWebview(BaseModel):
    type: Literal["webview"] = "webview"
    label: str
    url: str
    height: Literal["tall", "compact", "full"] | None = None
    trigger_node_id: str | None = None


MoveoCarouselButton = Union[
    _MoveoCarouselButtonPostback,
    _MoveoCarouselButtonURL,
    _MoveoCarouselButtonWebview,
]


class MoveoCarouselCard(BaseModel):
    title: str
    subtitle: str | None = None
    media: MoveoCarouselMedia
    buttons: list[MoveoCarouselButton] | None = None
    default_action: MoveoCarouselButton | None = None


class MoveoCarouselAction(BaseModel):
    type: Literal["carousel"] = "carousel"
    cards: list[MoveoCarouselCard]
    action_id: str


MoveoResponseAction = Union[
    MoveoTextAction,
    MoveoImageAction,
    MoveoVideoAction,
    MoveoAudioAction,
    MoveoFileAction,
    MoveoEventAction,
    MoveoWebviewAction,
    MoveoCarouselAction,
    MoveoResetAction,
]


# ---------------------------------------------------------------------------
# Agent-only actions — appear in the post-message `output` field but cannot
# be returned by your endpoint in `responses`. They are agent-internal.
# ---------------------------------------------------------------------------


class _MoveoAgentActionBase(BaseModel):
    """Properties present on every action in the agent's output."""

    model_config = ConfigDict(extra="allow")

    action_id: str | None = None
    metadata: dict[str, Any] | None = None


class MoveoHandoverAction(_MoveoAgentActionBase):
    type: Literal["handover"] = "handover"
    external: bool


class MoveoUrlAction(_MoveoAgentActionBase):
    type: Literal["url"] = "url"
    url: str


class MoveoTagAction(_MoveoAgentActionBase):
    type: Literal["tag"] = "tag"
    tags: list[str]


class MoveoReminderAction(_MoveoAgentActionBase):
    type: Literal["reminder"] = "reminder"
    reminder_seconds: int
    trigger_node_id: str


class MoveoWebhookCallAction(_MoveoAgentActionBase):
    type: Literal["webhook"] = "webhook"
    webhook_id: str
    fallback: list["MoveoAgentAction"]


MoveoAgentAction = Union[
    MoveoResponseAction,
    MoveoHandoverAction,
    MoveoUrlAction,
    MoveoTagAction,
    MoveoReminderAction,
    MoveoWebhookCallAction,
]


# Resolve forward references inside PostMessageWebhookRequest and the
# recursive MoveoWebhookCallAction.fallback field.
PostMessageWebhookRequest.model_rebuild()
MoveoWebhookCallAction.model_rebuild()


# ---------------------------------------------------------------------------
# Response bodies
# ---------------------------------------------------------------------------


class DialogWebhookResponse(BaseModel):
    """Either context, responses, or both. responses is validated against the
    action schema; invalid entries are skipped."""

    context: dict[str, Any] | None = None
    responses: list[MoveoResponseAction] | None = None


class PreMessageWebhookResponse(BaseModel):
    """Returns context updates and may rewrite the user input before the agent
    sees it. responses is not honored by this webhook type."""

    context: dict[str, Any] | None = None
    input: MoveoMessageInput | None = None


class FirstMessageWebhookResponse(PreMessageWebhookResponse):
    """Same shape as PreMessageWebhookResponse."""


class PostMessageWebhookResponse(BaseModel):
    """If responses is present, it replaces the agent's planned reply for
    this turn."""

    context: dict[str, Any] | None = None
    responses: list[MoveoResponseAction] | None = None
