# =============================================================================
# 企微IT智能服务台 — 消息模型
# =============================================================================
# 说明：对应数据库 messages 表，存储会话中的所有消息
# 消息来源：员工(employee)、坐席(agent)、AI(ai)、系统(system)
# 消息类型：文本(text)、图片(image)、文件(file)、语音(voice)、系统提示(system)
# =============================================================================

import uuid
from datetime import datetime, timedelta
from typing import Any, Dict, Optional

from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text
from sqlalchemy.orm import Mapped, mapped_column

from app.database import Base


class Message(Base):
    """消息模型 — 对应 messages 表。

    每条消息都属于一个会话（Conversation），记录对话中的每一条信息。
    包含发送者信息、消息内容、消息类型等。

    Attributes:
        id: 消息唯一标识（UUID，数据库自动生成）
        conversation_id: 所属会话ID（外键，关联 conversations 表）
        sender_type: 发送者类型（employee/agent/ai/system）
        sender_id: 发送者ID
        sender_name: 发送者姓名（冗余存储，减少关联查询）
        content: 消息内容（文本消息为文字，媒体消息为描述文字或URL）
        msg_type: 消息类型（text/image/file/voice/system）
        media_id: 企微媒体文件ID（图片/语音/视频消息，3天有效）
        media_url: 本地存储的媒体文件URL（下载后保存到服务器）
        file_name: 文件名（文件消息用）
        file_size: 文件大小（字节）
        extra_data: 扩展元数据（JSON，如图片尺寸、语音格式等）
        ai_suggestion: 是否为AI建议（坐席端展示用）
        status: 消息状态（sending/sent/delivered/read）
        recallable_until: 可撤回截止时间（创建时间+2分钟）
        is_read: 是否已读
        created_at: 创建时间
    """

    # 表名（必须和架构文档 DDL 一致）
    __tablename__ = "messages"

    # --------------------------------------------------------------------------
    # 字段定义
    # --------------------------------------------------------------------------

    # 主键：UUID，Python端生成（兼容PostgreSQL和SQLite）
    id: Mapped[str] = mapped_column(
        String(36),
        primary_key=True,
        default=lambda: str(uuid.uuid4()),
    )

    # 所属会话ID（外键，关联 conversations 表）
    # ON DELETE CASCADE：删除会话时自动删除该会话的所有消息
    conversation_id: Mapped[str] = mapped_column(
        String(36),
        ForeignKey("conversations.id", ondelete="CASCADE"),
        nullable=False,
        comment="所属会话ID",
    )

    # 发送者类型（CHECK 约束：只能取四种值）
    # employee: 员工发送的消息
    # agent: 坐席发送的消息
    # ai: AI生成的消息（第二步启用）
    # system: 系统消息（如"坐席已接入"等通知）
    sender_type: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        comment="发送者类型: employee/agent/ai/system",
    )

    # 发送者ID
    # 员工消息时为企微UserID，坐席消息时为坐席user_id
    sender_id: Mapped[str] = mapped_column(
        String(64),
        nullable=False,
        comment="发送者ID",
    )

    # 发送者姓名（冗余存储，避免每次查消息都要关联用户表）
    sender_name: Mapped[str] = mapped_column(
        String(128),
        nullable=False,
        default="",
        comment="发送者姓名",
    )

    # 消息内容
    # 文本消息时为文本内容，图片/文件消息时为媒体URL或描述文字
    content: Mapped[str] = mapped_column(
        Text,
        nullable=False,
        default="",
        comment="消息内容",
    )

    # 消息类型（CHECK 约束）
    # text: 文本消息
    # image: 图片消息
    # file: 文件消息
    # voice: 语音消息
    # system: 系统消息
    msg_type: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        default="text",
        comment="消息类型: text/image/file/voice/system",
    )

    # 引用回复：指向被回复的消息ID（M1 新增）
    # 为 None 时表示普通消息，非 None 时表示对某条消息的回复
    # 前端根据此字段显示引用内容（被回复消息的摘要）
    reply_to_id: Mapped[Optional[str]] = mapped_column(
        String(36),
        nullable=True,
        default=None,
        comment="引用回复：被回复的消息ID",
    )

    # 企微媒体文件ID（图片/语音/视频消息携带）
    # 注意：MediaId 仅3天有效，收到消息后应尽快下载保存到本地
    # 下载接口：GET https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=TOKEN&media_id=MEDIA_ID
    media_id: Mapped[Optional[str]] = mapped_column(
        String(256),
        nullable=True,
        default=None,
        comment="企微媒体文件ID（3天有效）",
    )

    # 本地存储的媒体文件URL（下载后保存到服务器/NAS的访问路径）
    # 格式示例：/media/2026/06/03/abc123.jpg
    media_url: Mapped[Optional[str]] = mapped_column(
        String(512),
        nullable=True,
        default=None,
        comment="本地存储的媒体文件URL",
    )

    # 文件名（文件消息携带，或下载后自定义的文件名）
    file_name: Mapped[Optional[str]] = mapped_column(
        String(256),
        nullable=True,
        default=None,
        comment="文件名",
    )

    # 文件大小（字节，文件消息携带）
    file_size: Mapped[Optional[int]] = mapped_column(
        Integer,
        nullable=True,
        default=None,
        comment="文件大小（字节）",
    )

    # 扩展元数据（JSON格式，存储各消息类型的额外信息）
    # 示例：
    #   图片消息: {"pic_url": "https://...", "width": 1920, "height": 1080}
    #   语音消息: {"format": "amr", "duration_seconds": 15}
    #   视频消息: {"thumb_media_id": "...", "duration_seconds": 30}
    #   位置消息: {"location_x": 23.134, "location_y": 113.358, "label": "杭州市"}
    extra_data: Mapped[Optional[Dict[str, Any]]] = mapped_column(
        JSON,
        nullable=True,
        default=None,
        comment="扩展元数据（JSON）",
    )

    # 是否为AI建议（坐席端展示用）
    # True: 此消息为AI建议的回复，坐席可选择采纳/编辑/忽略
    # False: 正常消息
    ai_suggestion: Mapped[bool] = mapped_column(
        Boolean,
        nullable=False,
        default=False,
        comment="是否为AI建议",
    )

    # 消息状态（新增：sending/sent/delivered/read）
    # sending: 发送中
    # sent: 已发送
    # delivered: 已送达
    # read: 已读
    status: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        default="sent",
        comment="消息状态: sending/sent/delivered/read",
    )

    # 可撤回截止时间（创���时间+2分钟）
    # 用于判断消息是否在可撤回时间窗口内
    recallable_until: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True),
        nullable=True,
        default=None,
        comment="可撤回截止时间",
    )

    # 是否已读
    # 用于统计未读消息数（坐席端显示红点）
    is_read: Mapped[bool] = mapped_column(
        Boolean,
        nullable=False,
        default=False,
        comment="是否已读",
    )

    # 坐席对 AI 建议的操作行为
    # 仅当 ai_suggestion=True 时有意义
    # accepted: 坐席直接采纳了AI建议
    # edited: 坐席编辑后采纳了AI建议
    # ignored: 坐席忽略了AI建议
    suggestion_action: Mapped[Optional[str]] = mapped_column(
        String(20),
        nullable=True,
        default=None,
        comment="AI建议操作行为: accepted/edited/ignored",
    )

    # 创建时间
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        default=datetime.now,
        comment="创建时间",
    )

    # --------------------------------------------------------------------------
    # 索引定义（和架构文档 DDL 严格一致）
    # --------------------------------------------------------------------------
    __table_args__ = (
        # 按会话ID查询（如查询某会话的所有消息）
        Index("idx_messages_conversation_id", "conversation_id"),
        # 按创建时间查询（如按时间排序消息）
        Index("idx_messages_created_at", "created_at"),
        # 复合索引：按会话+时间查询（最常见的查询：获取某会话的消息列表）
        Index("idx_messages_conversation_created", "conversation_id", "created_at"),
    )

    def __repr__(self) -> str:
        """消息对象的字符串表示，方便调试。"""
        return (
            f"<Message(id={self.id}, conv={self.conversation_id}, "
            f"from={self.sender_type}, type={self.msg_type})>"
        )