"""common.pool_timing — the ONE source of truth for the pool heartbeat metronome.

A tick is an AXIOM, never duplicated. Before this module the worker
(``pool_worker.py``) and the pool-service (``pool_service/pool_service.py``) each
declared their OWN ``HEARTBEAT_INTERVAL`` / ``PINGBACK_TIMEOUT`` and merely happened
to default to the same 25/30. Two declarations can drift; a drift here is not a
cosmetic bug — it false-evicts a LIVE, WORKING worker (or, the other way, lets a
dead one linger). The fix is to make the two consumers READ THE SAME VALUES from
here, and to make the safety invariant hold BY CONSTRUCTION rather than by
coincidence.

The fixed-tick law (operator, battle-tested):

  * The heartbeat is a METRONOME — set ONCE at launch and NEVER live-tuned or made
    dynamic. Liveness is not a load signal.
  * It is LOAD-INDEPENDENT. A dynamically-elastic beat feeds back into the very
    load it measures and smears spikes flat; it is also ill-posed to size. So the
    beat lives ONLY as a launch constant and is in every consumer's
    config-safety set (never reachable from the live config-service).
  * SAFETY INVARIANT: ``HEARTBEAT_INTERVAL < PINGBACK_TIMEOUT`` must hold BY
    CONSTRUCTION. A too-slow beat (beat >= pingback) means the worker cannot get
    a ping in before the pool-service's freshness window lapses, so a live worker
    is reaped between beats. We assert this at IMPORT time (below): a config that
    violates it raises immediately and can NEVER start a worker or the pool-service
    in an unsafe state — fail-closed, not fail-quiet.

Env contract (read here, ONCE, so both consumers agree):

    PS_HEARTBEAT_INTERVAL   worker-side canonical name (seconds; default 25)
    PS_PINGBACK_TIMEOUT     worker-side canonical name (seconds; default 30)
    PINGBACK_TIMEOUT        pool-service legacy name (compose maps
                            PINGBACK_TIMEOUT="${PS_PINGBACK_TIMEOUT:-30}", so the
                            pool-service container receives the bare name); read as
                            a fallback so the deployed service keeps working with no
                            compose change.

Stdlib-only by design: this module is COPYd into both the worker images
(``COPY common/`` … ) and the pool-service image (``COPY common/pool_timing.py``),
so it must never pull a third-party dep.
"""
from __future__ import annotations

import os

# Minimum safety margin (seconds) between the beat and the pingback window. The
# beat thread does real work each tick (HTTP register + Redis touch) and the
# network/scheduler adds jitter; the window must comfortably clear one beat plus
# that jitter, not merely exceed it by an epsilon. One full second is the floor.
HEARTBEAT_SAFETY_MARGIN = int(os.getenv("PS_HEARTBEAT_SAFETY_MARGIN", "1") or "1")

# Worker uses PS_HEARTBEAT_INTERVAL; there is no legacy bare alias for the beat.
HEARTBEAT_INTERVAL = int(os.getenv("PS_HEARTBEAT_INTERVAL", "25"))

# Worker uses PS_PINGBACK_TIMEOUT; the pool-service container historically receives
# the bare PINGBACK_TIMEOUT (compose maps it from PS_PINGBACK_TIMEOUT). Read the
# canonical name first, fall back to the legacy name, then the 30s default — so BOTH
# consumers resolve to the same single value regardless of which name their env set.
PINGBACK_TIMEOUT = int(
    os.getenv("PS_PINGBACK_TIMEOUT")
    or os.getenv("PINGBACK_TIMEOUT")
    or "30"
)

# ── Enforce the safety invariant BY CONSTRUCTION, at import time ──────────────────
# A bad config (beat >= pingback, or no safe margin) must never start a worker or the
# pool-service. Raise a clear, actionable error instead of letting an unsafe metronome
# quietly false-reap live workers in production.
if not (HEARTBEAT_INTERVAL + HEARTBEAT_SAFETY_MARGIN <= PINGBACK_TIMEOUT):
    raise RuntimeError(
        "pool heartbeat SAFETY INVARIANT violated: "
        f"HEARTBEAT_INTERVAL ({HEARTBEAT_INTERVAL}s) + margin "
        f"({HEARTBEAT_SAFETY_MARGIN}s) must be <= PINGBACK_TIMEOUT "
        f"({PINGBACK_TIMEOUT}s). A beat that does not fit inside the pingback "
        "window false-evicts a LIVE worker. Fix PS_HEARTBEAT_INTERVAL / "
        "PS_PINGBACK_TIMEOUT (worker) or PINGBACK_TIMEOUT (pool-service) so the "
        "beat clears the window with margin. The tick is a fixed launch constant; "
        "it is never live/dynamic."
    )

__all__ = ["HEARTBEAT_INTERVAL", "PINGBACK_TIMEOUT", "HEARTBEAT_SAFETY_MARGIN"]
