"""Tracks whether the previous digest run was actually looked at.

If it wasn't, the next run folds its content into the new one instead of quietly
discarding it — see run.py's should_merge()/previous_section_document() and
synth/llm_client.py's `previous_unviewed_digest` handling.

The signal comes from Home Assistant/thinclient-agent over MQTT rather than a local
file: the thing that knows whether a digest was actually shown is
hosts/thin-client/agent/thinclient_agent/digest_canvas.py, which runs on a different
physical machine (the thin client) from this one. MQTT is already this project's
cross-host channel for exactly this kind of fact — the same broker Home Assistant and
every thinclient-agent already use — so this reuses it rather than inventing a second
one.

This module only ever subscribes, never publishes. Publishing "viewed" is
thinclient-agent's job (on_show_digest() in its main.py), triggered only by an actual
full-canvas display (the "Show digest canvas" button or a voice-resolved "play my
digest" request) — not by the compact HA-dashboard iframe view, which never reaches
thinclient-agent at all and so cannot mark anything as viewed. That's deliberate: an
iframe sitting open on someone's phone is a much weaker "was this looked at" signal
than a thin client actually switching workspace and displaying it.
"""

import json
import logging
import os
import threading

LOG = logging.getLogger(__name__)

VIEWED_TOPIC = "smarthome/digest/viewed"


def last_viewed_at():
    """ISO timestamp of the last time any thin client showed a digest, or None.

    None covers every failure mode identically on purpose (no broker reachable,
    nothing ever published, a malformed retained payload) — the caller treats
    "unknown" the same as "viewed", the safe default. Treating unknown as "unviewed"
    instead would mean a broken MQTT link silently glues every run onto the last one
    forever; treating it as "viewed" just means at most one run's content is dropped
    before someone notices the broker is unreachable, and OPNsense-style backstops
    elsewhere in this codebase never take the riskier default either.
    """
    host = os.environ.get("MQTT_BROKER_HOST", "mosquitto")
    port = int(os.environ.get("MQTT_BROKER_PORT", "1883"))
    username = os.environ.get("MQTT_USERNAME") or None
    password = os.environ.get("MQTT_PASSWORD") or None
    wait_seconds = float(os.environ.get("MQTT_VIEWED_WAIT_SECONDS", "3"))

    try:
        import paho.mqtt.client as mqtt
    except ImportError:
        LOG.warning("paho-mqtt is not installed; cannot check whether the last digest was viewed")
        return None

    result = {"value": None}
    received = threading.Event()

    def on_message(_client, _userdata, message):
        try:
            payload = json.loads(message.payload.decode("utf-8"))
            result["value"] = payload.get("viewed_at")
        except (ValueError, AttributeError):
            LOG.warning("malformed retained payload on %s: %r", VIEWED_TOPIC, message.payload)
        received.set()

    def on_connect(client, _userdata, _flags, rc, *_args):
        if rc != 0:
            LOG.warning("could not connect to MQTT broker %s:%s (rc=%s)", host, port, rc)
            received.set()
            return
        client.subscribe(VIEWED_TOPIC, qos=1)

    # Mirrors thinclient_agent.main.make_client's own VERSION1/VERSION2 handling —
    # bookworm's python3-paho-mqtt is 1.6.x, but this runs in digest-engine's own
    # container image, which may end up with a newer pip-installed paho-mqtt.
    callback_api = getattr(mqtt, "CallbackAPIVersion", None)
    client = mqtt.Client(callback_api.VERSION1) if callback_api is not None else mqtt.Client()
    if username:
        client.username_pw_set(username, password)
    client.on_connect = on_connect
    client.on_message = on_message

    try:
        client.connect(host, port, keepalive=10)
        client.loop_start()
        # A retained message arrives within a fraction of a second of subscribing, if
        # one exists at all. The wait is bounded so an unreachable broker cannot stall
        # the whole digest run — the same "never block on a remote service" rule
        # thinclient-agent's own connect_async already applies to itself.
        received.wait(wait_seconds)
    except Exception:
        LOG.warning("could not reach MQTT broker %s:%s", host, port, exc_info=True)
    finally:
        client.loop_stop()
        client.disconnect()

    return result["value"]
