"""Task: Device heartbeat health check (runs every 60s via arq cron).

Modeled on ``monitor_worker_fleet`` (src/workers/tasks/fleet_monitor.py): devices that have
missed heartbeats past the cutoff are flagged ``offline`` and a ``DeviceEvent`` is recorded.

Per the plan, a device going offline does NOT touch its in-flight ``CodegenRun`` rows — they
stay at their last checkpoint. A reconnecting device resumes by polling
``GET /api/devices/{device_id}/runs?status=active``.
"""
from __future__ import annotations

from datetime import datetime, timedelta, timezone

import structlog

logger = structlog.get_logger()

# No heartbeat for this long -> considered offline. Cron itself runs every 60s (see
# src/workers/main.py); ~3x the expected heartbeat interval gives room for one missed beat.
DEVICE_OFFLINE_AFTER_SECONDS = 180


async def monitor_device_health(ctx: dict) -> dict:
    from src.core.database import async_session_factory
    from src.domain.devices.repository import DeviceEventRepository, DeviceRepository

    db_factory = ctx.get("db_factory", async_session_factory)
    flagged_offline = 0

    async with db_factory() as db:
        devices = DeviceRepository(db)
        events = DeviceEventRepository(db)

        cutoff = datetime.now(timezone.utc) - timedelta(seconds=DEVICE_OFFLINE_AFTER_SECONDS)
        stale = await devices.list_stale_active(cutoff)

        for device in stale:
            await devices.mark_offline(device)
            await events.create(
                device.id,
                event_type="health_failed",
                triggered_by="health_monitor",
                metadata={"last_heartbeat_at": device.last_heartbeat_at.isoformat() if device.last_heartbeat_at else None},
            )
            flagged_offline += 1
            logger.warning(
                "device_health_failed",
                device_id=str(device.id),
                last_heartbeat_at=device.last_heartbeat_at.isoformat() if device.last_heartbeat_at else None,
            )

        await db.commit()

    return {"status": "ok", "flagged_offline": flagged_offline}
