"""device 表读写(设备注册 / 心跳 / 超时扫描)。"""
from __future__ import annotations

from datetime import datetime, timedelta, timezone

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.device import DeviceLiveness


def _get(db: Session, *, user_id: int, device_id: str) -> DeviceLiveness | None:
    stmt = select(DeviceLiveness).where(
        DeviceLiveness.user_id == user_id, DeviceLiveness.device_id == device_id
    )
    return db.execute(stmt).scalar_one_or_none()


def register_or_update(
    db: Session,
    *,
    user_id: int,
    device_id: str,
    registration_id: str | None = None,
    push_vendor: str | None = None,
    push_token: str | None = None,
    platform: str = "android",
    app_version: str | None = None,
) -> DeviceLiveness:
    """注册设备或更新其厂商 push token / 元信息。upsert by (user_id, device_id)。"""
    normalized_vendor = _normalize_push_vendor(push_vendor)
    normalized_token = push_token.strip() if push_token else None
    device = _get(db, user_id=user_id, device_id=device_id)
    if device is None:
        device = DeviceLiveness(
            user_id=user_id,
            device_id=device_id,
            registration_id=registration_id,
            push_vendor=normalized_vendor,
            push_token=normalized_token,
            platform=platform or "android",
            app_version=app_version,
        )
        db.add(device)
    else:
        if registration_id:
            device.registration_id = registration_id
        if normalized_vendor:
            device.push_vendor = normalized_vendor
        if normalized_token:
            device.push_token = normalized_token
        if platform:
            device.platform = platform
        if app_version:
            device.app_version = app_version
    db.commit()
    db.refresh(device)
    return device


def touch_heartbeat(
    db: Session,
    *,
    user_id: int,
    device_id: str,
    accessibility_enabled: bool,
    registration_id: str | None = None,
    push_vendor: str | None = None,
    push_token: str | None = None,
) -> DeviceLiveness:
    """处理一次心跳(心跳也能自注册)。

    service 心跳或 accessibility_enabled=true 时,刷新存活并把状态机重置回 alive、
    清掉 notified_at(掉线恢复 → 下次再断才会再推一条)。
    """
    now = datetime.now(timezone.utc)
    device = _get(db, user_id=user_id, device_id=device_id)
    if device is None:
        device = DeviceLiveness(user_id=user_id, device_id=device_id)
        db.add(device)

    if registration_id:
        device.registration_id = registration_id
    normalized_vendor = _normalize_push_vendor(push_vendor)
    normalized_token = push_token.strip() if push_token else None
    if normalized_vendor:
        device.push_vendor = normalized_vendor
    if normalized_token:
        device.push_token = normalized_token
    device.last_report_protection_on = accessibility_enabled

    if accessibility_enabled:
        if not device.ever_protected:
            device.first_protected_at = now  # 首次开无障碍记一次,后续心跳不覆盖
        device.last_heartbeat_at = now
        device.ever_protected = True
        device.liveness_state = "alive"
        device.notified_at = None

    db.commit()
    db.refresh(device)
    return device


def list_overdue(db: Session, *, timeout_minutes: int) -> list[DeviceLiveness]:
    """掉线设备:曾经保护过、当前 alive、心跳超时。

    即使没有厂商 token 也要检出,后续由 kill_alert_pending 走客户端进 App 后兜底提醒。
    """
    cutoff = datetime.now(timezone.utc) - timedelta(minutes=timeout_minutes)
    stmt = select(DeviceLiveness).where(
        DeviceLiveness.ever_protected.is_(True),
        DeviceLiveness.liveness_state == "alive",
        DeviceLiveness.last_heartbeat_at.is_not(None),
        DeviceLiveness.last_heartbeat_at < cutoff,
    )
    return list(db.execute(stmt).scalars().all())


def mark_notified(db: Session, *, device_id_pk: int) -> None:
    """标记掉线已检出(状态机进入 notified,避免每轮重复处理)+ 置「待客户端提醒」标记。

    kill_alert_pending 与 state 解耦(见 spec accessibility-liveness-pull-prompt.md):
    心跳恢复(touch_heartbeat)只重置 state、不动此标记,故客户端下次进 App 必能拉到这次掉线。
    """
    device = db.get(DeviceLiveness, device_id_pk)
    if device is not None:
        device.liveness_state = "notified"
        device.notified_at = datetime.now(timezone.utc)
        device.kill_alert_pending = True
        db.commit()


def get_device(db: Session, *, user_id: int, device_id: str) -> DeviceLiveness | None:
    """按 (user_id, device_id) 取设备(liveness 查询用)。"""
    return _get(db, user_id=user_id, device_id=device_id)


def ack_kill_alert(db: Session, *, user_id: int, device_id: str) -> None:
    """客户端已弹过「开启自启动」引导 → 清「待提醒」标记(幂等; 下次真掉线 worker 再置)。"""
    device = _get(db, user_id=user_id, device_id=device_id)
    if device is not None and device.kill_alert_pending:
        device.kill_alert_pending = False
        db.commit()


def has_push_target(device: DeviceLiveness | None) -> bool:
    """是否已有厂商直推所需的 vendor + token。"""
    return bool(device and device.push_vendor and device.push_token)


def list_push_targets(db: Session, *, user_id: int) -> list[DeviceLiveness]:
    """该用户全部可用厂商推送目标(push_vendor + push_token 双非空),最近更新在前。

    同 (vendor, token) 只留最新一行:同一台手机重装 App 后 device_id 会变、
    留下 token 相同的旧行,去重防一次业务事件对同一台手机重复推送。
    """
    stmt = (
        select(DeviceLiveness)
        .where(
            DeviceLiveness.user_id == user_id,
            DeviceLiveness.push_vendor.is_not(None),
            DeviceLiveness.push_token.is_not(None),
        )
        .order_by(DeviceLiveness.updated_at.desc(), DeviceLiveness.id.desc())
    )
    seen: set[tuple[str, str]] = set()
    targets: list[DeviceLiveness] = []
    for dev in db.execute(stmt).scalars():
        if not dev.push_vendor or not dev.push_token:  # 空串兜底(旧数据)
            continue
        key = (dev.push_vendor, dev.push_token)
        if key in seen:
            continue
        seen.add(key)
        targets.append(dev)
    return targets


def _normalize_push_vendor(push_vendor: str | None) -> str | None:
    if not push_vendor:
        return None
    vendor = push_vendor.strip().lower()
    aliases = {
        "honor": "honor",
        "hihonor": "honor",
        "荣耀": "honor",
        "vivo": "vivo",
        "xiaomi": "xiaomi",
        "mi": "xiaomi",
        "小米": "xiaomi",
        "oppo": "oppo",
        "oneplus": "oppo",
        "realme": "oppo",
    }
    return aliases.get(vendor, vendor)
