"""Operational state the API can read but the worker owns (task 5.6).

The API and the worker are separate containers with no channel between them, so "is the
worker alive?" cannot be answered by asking it. The worker stamps a row every tick and the
health endpoint reads it. A stale stamp is the only signal that reminders have silently
stopped being planned — without it the first symptom would be guests not receiving a
T-2 reminder, discovered after the wedding.
"""

from datetime import datetime

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

from app.models.base import Base, TimestampTZ, created_at_col, updated_at_col


class WorkerHeartbeat(Base):
    """One row per worker task name, upserted on every successful tick."""

    __tablename__ = "worker_heartbeat"

    #: The APScheduler job id — natural key, so a tick is a plain upsert with no lookup.
    name: Mapped[str] = mapped_column(String(60), primary_key=True)
    beat_at: Mapped[datetime] = mapped_column(TimestampTZ, nullable=False)
    #: Monotonic per worker process; a counter that stops climbing while `beat_at` moves
    #: would mean the row is being written by something other than a live loop.
    ticks: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    last_error: Mapped[str | None] = mapped_column(String(500))

    created_at: Mapped[datetime] = created_at_col()
    updated_at: Mapped[datetime] = updated_at_col()
