"""消息通知中心 endpoint(PRD《消息通知中心》)。

路由前缀 `/api/v1/notifications`,需 Bearer 鉴权(消息按用户隔离)。
  GET  /              消息列表(分页;全列表时间倒序,不分组——PRD 原文的分组已取消)
  GET  /unread-count  未读总数(首页铃铛角标)
  POST /read          标记已读({ids:[...]} 单条/多条 或 {all:true} 全量清零)

数据落库 `notification` 表(repositories/notification.py,按用户隔离)。业务事件(奖励过期、
提现回执、反馈回复……)调 `create_notification` 下发;未接入业务前列表为空,可用
`/api/v1/push/test` 的 createNotification 造联调数据。

⚠️ 字段命名:本组接口对外为 **camelCase**(sentAt / isRead / pageSize…,PRD 前端契约),
详见 schemas/notification.py 顶部说明。
"""
from __future__ import annotations

import logging

from fastapi import APIRouter, HTTPException, Query

from app.api.deps import CurrentUser, DbSession
from app.core import notification_catalog as catalog
from app.models.notification import Notification
from app.repositories import notification as notif_repo
from app.schemas.notification import (
    InfoRow,
    MarkReadOut,
    MarkReadRequest,
    NotificationItem,
    NotificationListOut,
    UnreadCountOut,
)

logger = logging.getLogger("shagua.notifications")

router = APIRouter(prefix="/api/v1/notifications", tags=["notifications"])


def _to_item(n: Notification) -> NotificationItem:
    """通知行 + 类型静态目录 → 接口出参。"""
    ntype = catalog.get_type(n.type)
    return NotificationItem(
        id=n.id,
        category=ntype.category,
        category_label=catalog.category_label(ntype.category),
        type=ntype.key,
        card_style=ntype.card_style,
        title=ntype.card_title,
        coins=n.coins,
        cash_cents=n.cash_cents,
        cash_yuan=notif_repo.cash_yuan(n.cash_cents),
        info_rows=[InfoRow(**row) for row in n.info_rows],
        action_text=ntype.action_text,
        extra=n.extra,
        sent_at=notif_repo.as_cst(n.sent_at),
        is_read=n.is_read,
    )


@router.get("", response_model=NotificationListOut, summary="消息列表(分页)")
def list_notifications(
    user: CurrentUser,
    db: DbSession,
    page: int = Query(default=1, ge=1, description="页码,1 起"),
    page_size: int = Query(
        default=20, ge=1, le=100, alias="pageSize", description="每页条数,默认 20,最大 100"
    ),
) -> NotificationListOut:
    """通知中心消息列表。

    - 排序服务端已做好:**全列表按时间倒序**(最新在前,不做分类分组;PRD §1 的
      "按分类分组"为笔误,已与需求方确认取消),前端按返回顺序渲染即可。
    - 每条的字段构成与各版式说明见 NotificationItem schema。
    - 响应同时带 unreadCount,进页面时可顺手刷新角标。
    - 无消息时返回空列表(total=0);数据由业务事件下发,联调可用 /push/test 造。
    """
    items, total, unread = notif_repo.list_notifications(
        db, user.id, page=page, page_size=page_size
    )
    return NotificationListOut(
        items=[_to_item(n) for n in items],
        page=page,
        page_size=page_size,
        total=total,
        has_more=page * page_size < total,
        unread_count=unread,
    )


@router.get("/unread-count", response_model=UnreadCountOut, summary="未读总数(铃铛角标)")
def get_unread_count(user: CurrentUser, db: DbSession) -> UnreadCountOut:
    """首页铃铛角标数据源。刷新时机(PRD §4):进入首页时、从通知中心/其他页面返回首页时。

    - count:精确未读条数;
    - badgeText:直接可展示的角标文案——超过 99 返回 "99+",等于 0 返回 null(隐藏整个角标)。
    """
    count = notif_repo.unread_count(db, user.id)
    badge = None if count == 0 else ("99+" if count > 99 else str(count))
    return UnreadCountOut(count=count, badge_text=badge)


@router.post("/read", response_model=MarkReadOut, summary="标记已读(单条/多条/全量)")
def mark_read(req: MarkReadRequest, user: CurrentUser, db: DbSession) -> MarkReadOut:
    """红点消除(PRD §4),两种调用模式:

    1. `{"ids": [90001]}` —— 点击某张消息卡片(无论点击后是跳转/弹窗/无动作都算已读);
       用户点击 push 直达落地页时,客户端也用它把对应站内消息同步置读(push extras 里带
       notificationId);
    2. `{"all": true}` —— 进入通知中心自动清零(只是浏览列表就消红点,无需逐条点击)。

    幂等:不存在或已读的 id 忽略;重复调用 markedCount 为 0、不报错。
    响应带 unreadCount(处理后剩余未读),可直接刷新铃铛角标。
    """
    if not req.all and not req.ids:
        raise HTTPException(status_code=400, detail="ids 与 all 至少传一个:{ids:[...]} 或 {all:true}")
    marked, unread = notif_repo.mark_read(db, user.id, ids=req.ids, mark_all=req.all)
    logger.info(
        "notifications read user_id=%d mode=%s marked=%d unread_left=%d",
        user.id,
        "all" if req.all else f"ids×{len(req.ids or [])}",
        marked,
        unread,
    )
    return MarkReadOut(ok=True, marked_count=marked, unread_count=unread)
