"""消息通知中心 请求/响应契约。

⚠️ 命名约定:本组接口按 PRD 前端契约使用 **camelCase**(sentAt / isRead / pageSize …),
与库内其他 snake_case 接口不同——PRD 与前端原型(notifications.html)按 camelCase 对接,
需求方接口清单亦明确写作 sentAt / isRead,故整组遵循之。响应序列化走 pydantic alias。

字段说明都写在 Field(description=...) 里,起服务后打开 /docs 即是给前端的在线文档。
"""
from __future__ import annotations

from datetime import datetime
from typing import Any

from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel


class _CamelModel(BaseModel):
    """出参统一 camelCase(alias);populate_by_name 允许服务端代码仍用 snake_case 构造。"""

    model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)


class InfoRow(_CamelModel):
    """卡片信息行(PRD §3「信息行」列),前端按 label: value 逐行渲染。"""

    label: str = Field(description="行标签,如「过期说明」「到账账户」「失败原因」")
    value: str = Field(description="行内容(已按 PRD 文案拼好变量,前端直接展示)")


class NotificationItem(_CamelModel):
    """一条通知卡片。

    卡片头部三要素:categoryLabel(分类标签)+ 未读红点(isRead=false 时展示)+ 时间(sentAt)。
    时间显示规则(前端处理):今天→「今天」;昨天→「昨天」;当年→「M月D日」;跨年→「YYYY年M月D日」。
    """

    id: int = Field(description="通知 id(未读消除、push 联动都用它)")
    category: str = Field(
        description="分类 key:withdraw_assistant=提现助手 / system=系统通知 / "
                    "feedback=我的反馈 / report=我的爆料 / invite=好友邀请"
    )
    category_label: str = Field(description="分类中文标签(卡片头部直接展示)")
    type: str = Field(
        description="类型 key(13 种,决定点击行为,见 PRD §2):reward_expiring 即将失效 / "
                    "reward_expired 已失效 / withdraw_success 提现成功 / withdraw_failed 提现失败 / "
                    "perm_accessibility 无障碍异常 / perm_battery 省电策略异常 / "
                    "perm_autostart 自启动异常 / perm_overlay 悬浮窗异常 / "
                    "feedback_reply 官方回复 / feedback_reward 反馈奖励 / "
                    "report_approved 爆料审核通过 / invite_order_reward 好友下单奖励 / "
                    "invite_remind 好友催单提醒"
    )
    card_style: str = Field(
        description="卡片版式:dual_amount 双金额卡 / withdraw 提现卡 / plain_text 纯文本卡 / "
                    "coin_reward 金币奖励卡 / friend_cash 好友现金卡"
    )
    title: str = Field(description="卡片标题(双金额/提现/金币奖励/好友现金卡标题居中)")
    coins: int | None = Field(
        default=None,
        description="金币数(整数,不带小数)。dual_amount / coin_reward 卡有值,其余 null",
    )
    cash_cents: int | None = Field(
        default=None,
        description="现金金额,单位【分】。dual_amount / withdraw / friend_cash 卡有值,其余 null",
    )
    cash_yuan: str | None = Field(
        default=None,
        description="现金金额展示串(元,保留两位小数,如 \"12.80\"),与 cashCents 同源,可直接展示",
    )
    info_rows: list[InfoRow] = Field(
        description="信息行列表(label: value),内容已按 PRD §3 拼好,前端逐行渲染即可"
    )
    action_text: str | None = Field(
        default=None,
        description="操作行文案(如「立即激活您的收益」「去开启」);null=无操作行(提现成功卡)。"
                    "注意:点击目标是整张卡片,不区分卡片主体和操作行",
    )
    extra: dict[str, Any] = Field(
        description="点击跳转所需业务参数,按 type 取用:perm_* → {permission: accessibility|battery|"
                    "autostart|overlay}(点击时实时检测该权限);feedback_* → {feedbackId};"
                    "report_approved → {reportId};withdraw_failed → {withdrawId};"
                    "invite_order_reward → {inviteeNickname};invite_remind → "
                    "{inviteeNickname, scrollTo:\"remind\"};reward_expiring → {batchId}"
    )
    sent_at: datetime = Field(description="下发时间(ISO8601 带 +08:00 时区),前端按显示规则格式化")
    is_read: bool = Field(description="是否已读;false 时分类标签右侧展示 6px 红点(#E53935)")


class NotificationListOut(_CamelModel):
    """GET /api/v1/notifications 出参。列表已按时间倒序排好(最新在前,**不分组**;
    PRD §1 的"按分类分组"为笔误,已确认取消),前端无需再排。"""

    items: list[NotificationItem] = Field(description="当前页通知卡片")
    page: int = Field(description="当前页码(1 起)")
    page_size: int = Field(description="每页条数")
    total: int = Field(description="全部通知总条数(含已读)")
    has_more: bool = Field(description="是否还有下一页")
    unread_count: int = Field(description="当前未读总数(与 /notifications/unread-count 同口径,省一次请求)")


class UnreadCountOut(_CamelModel):
    """GET /api/v1/notifications/unread-count 出参(首页铃铛角标)。"""

    count: int = Field(description="未读总条数(精确值)")
    badge_text: str | None = Field(
        description="角标展示文案:超过 99 返回 \"99+\";等于 0 返回 null(整个角标隐藏,不展示空红点)"
    )


class MarkReadRequest(_CamelModel):
    """POST /api/v1/notifications/read 入参,两种模式二选一:

    - `{"ids": [90001, 90002]}` 单条/多条置读——点击某张卡片、点击 push 落地后同步置读;
    - `{"all": true}`           全量清零——进入通知中心(或退出时)自动清零(PRD §4)。

    同时传时 all=true 优先;不存在/已读的 id 自动忽略(幂等,可放心重试)。
    """

    ids: list[int] | None = Field(default=None, description="要置为已读的通知 id 列表")
    all: bool = Field(default=False, description="true=清空该用户全部未读")


class MarkReadOut(_CamelModel):
    """POST /api/v1/notifications/read 出参。"""

    ok: bool = Field(description="固定 true(参数非法时走 400,不会到这里)")
    marked_count: int = Field(description="本次实际由未读变为已读的条数(重复请求会是 0)")
    unread_count: int = Field(description="处理后的剩余未读总数,可直接刷新铃铛角标")
