"""消息通知中心:站内消息表(一行 = 一条下发给某用户的站内消息)。

13 类通知的**静态定义**(分类 / 版式 / 标题 / 操作行 / push 模板)在
`app/core/notification_catalog.py`,是代码常量,**不入库**;本表只存**每条消息的动态部分**
(与接口 NotificationItem 的动态字段一一对应):type + 金额 + 信息行 + extra + 已读态 + 时间。
category / card_style / title / action_text 都由 `type` 经 catalog 派生,不冗余存库。

- 写:`repositories/notification.create_notification`(业务事件下发站内消息的统一入口)。
- 读:`api/v1/notifications.py`(列表 / 未读数 / 标记已读),均按 user 隔离、sent_at 倒序。
"""
from __future__ import annotations

from datetime import datetime

from sqlalchemy import (
    JSON,
    Boolean,
    DateTime,
    ForeignKey,
    Index,
    Integer,
    String,
    func,
    text,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column

from app.db.base import Base

# PG 用 JSONB,SQLite(本地/测试)退化为通用 JSON(同 comparison_record.raw_payload 等)。
_JSON = JSON().with_variant(JSONB(), "postgresql")


class Notification(Base):
    __tablename__ = "notification"
    __table_args__ = (
        # 列表分页:按用户取 + sent_at 倒序(核心查询,覆盖 user_id 前缀查找,故不再单独索引 user_id)
        Index("ix_notification_user_sent", "user_id", "sent_at"),
        # 铃铛角标:count where user_id=? and is_read=false —— 部分索引只覆盖未读行
        Index(
            "ix_notification_user_unread",
            "user_id",
            sqlite_where=text("is_read = 0"),
            postgresql_where=text("is_read = false"),
        ),
        # 去重/合并:同一 (user, type, dedup_key) 未读期间只允许一条(perm_* 权限异常、
        # reward_expiring 同批次即用它);消息一旦已读即离开索引,之后可再生成新的未读消息。
        Index(
            "uq_notification_user_type_dedup",
            "user_id",
            "type",
            "dedup_key",
            unique=True,
            sqlite_where=text("dedup_key IS NOT NULL AND is_read = 0"),
            postgresql_where=text("dedup_key IS NOT NULL AND is_read = false"),
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(Integer, ForeignKey("user.id"), nullable=False)
    # 13 类之一(catalog.TYPES 的 key);category/card_style/title/action_text 由它派生,不入库
    type: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
    # 金币数(dual_amount / coin_reward 卡);其余类型 None
    coins: Mapped[int | None] = mapped_column(Integer, nullable=True)
    # 现金,单位【分】(dual_amount / withdraw / friend_cash 卡);其余 None
    cash_cents: Mapped[int | None] = mapped_column(Integer, nullable=True)
    # 信息行 [{label, value}](已渲染好文案,前端逐行展示)
    info_rows: Mapped[list] = mapped_column(_JSON, nullable=False, default=list)
    # 点击跳转/联动参数(feedbackId / withdrawId / permission / inviteeNickname / batchId …)
    extra: Mapped[dict] = mapped_column(_JSON, nullable=False, default=dict)
    is_read: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
    # 置读时刻(未读时为 None;埋点/分析用)
    read_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    # 去重键(可空):perm_*→permission、reward_expiring→batchId 等;配合部分唯一索引防重复未读
    dedup_key: Mapped[str | None] = mapped_column(String(64), nullable=True)
    # 下发/业务时间;列表排序与展示都用它(带 +08:00 下发)
    sent_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), nullable=False
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), nullable=False
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        server_default=func.now(),
        onupdate=func.now(),
        nullable=False,
    )

    def __repr__(self) -> str:  # pragma: no cover
        return (
            f"<Notification id={self.id} user_id={self.user_id} "
            f"type={self.type} read={self.is_read}>"
        )
