"""设备表(无障碍保护存活检测 + 极光推送)。

每条 = 一个用户的一台设备(per-install,device_id 由客户端 DeviceId.get() 生成)。
客户端的无障碍服务存活时周期上报心跳刷新 last_heartbeat_at;App 前台/登录时上报
registration_id(极光推送目标)。后端 heartbeat_monitor_worker 扫描「曾经保护过、
现在心跳超时」的设备,通过极光推送提醒用户重开无障碍。

liveness_state 状态机(防刷屏,一次掉线只推一条):
  unknown → alive(收到 service 心跳)→ silent/notified(扫描发现超时并已推送)
  心跳恢复时 handler 重置回 alive。
见 spec: spec/accessibility-liveness-push.md。
"""
from __future__ import annotations

from datetime import datetime

from sqlalchemy import (
    Boolean,
    DateTime,
    ForeignKey,
    Integer,
    String,
    UniqueConstraint,
    func,
)
from sqlalchemy.orm import Mapped, mapped_column

from app.db.base import Base


class DeviceLiveness(Base):
    # 表名不叫 device:device 易被当成「设备信息(品牌/型号/系统)」表;本表实为**无障碍存活监控状态**
    # (心跳 last_heartbeat_at + liveness_state + kill_alert_pending + 推送目标 registration_id),故名 device_liveness。
    __tablename__ = "device_liveness"
    __table_args__ = (
        UniqueConstraint("user_id", "device_id", name="uq_device_liveness_user_device"),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("user.id"), index=True, nullable=False
    )
    # 客户端 DeviceId.get() 生成的 per-install id(如 device_Pixel_ab12cd34)
    device_id: Mapped[str] = mapped_column(String(128), index=True, nullable=False)
    # 极光推送 registration id;拿到才填(JCollectionAuth 同意后才下发)
    registration_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
    platform: Mapped[str] = mapped_column(String(16), nullable=False, default="android")
    app_version: Mapped[str | None] = mapped_column(String(32), nullable=True)

    # 收到过 service 心跳即 true(=该设备开过无障碍,功能对它有意义)
    ever_protected: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=False
    )
    # 首次开无障碍(首次收到 accessibility_enabled 心跳)的时刻;ever_protected 第一次翻 true 时记一次,
    # 后续心跳不覆盖。老设备(迁移前已 protected)无此值 → NULL。
    first_protected_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    # 最近一次 service 心跳时间(存活证明);超时即视为保护掉线
    last_heartbeat_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), index=True, nullable=True
    )
    # 最近一次上报的无障碍开关状态(观测用)
    last_report_protection_on: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=False
    )
    # unknown / alive / silent / notified
    liveness_state: Mapped[str] = mapped_column(
        String(16), nullable=False, default="unknown"
    )
    # 最近一次推送告警时间
    notified_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    # 掉线告警「待客户端提醒」标记(后置检测 pull 版, 见 spec accessibility-liveness-pull-prompt.md)。
    # 与 liveness_state 解耦: worker 检出掉线即置 True; touch_heartbeat(心跳恢复)不动它
    # → 规避「服务随 App 重启先发心跳、state 被重置回 alive → 客户端进 App 漏看」竞态; 只由客户端 ack 清。
    kill_alert_pending: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=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"<DeviceLiveness id={self.id} user_id={self.user_id} "
            f"device_id={self.device_id} state={self.liveness_state}>"
        )
