"""Owner-facing Telegram notifications for factory runs.

Shells out to the `botmaster` CLI (modules/botmaster/notify), which already
resolves the "Overdeck" channel and sends via Telegram. Every call here is
FAIL-OPEN: a notify failure is a stderr warning, never a reason to block or
fail a factory run.
"""

from __future__ import annotations

import os
import subprocess
import sys

CHANNEL = "Overdeck"
SEND_TIMEOUT_S = 10
_OFF_VALUES = {"off", "0", "false", "no"}


def _disabled() -> bool:
    """Structural test isolation: FACTORY_NOTIFY=off short-circuits every send.

    tests/conftest.py sets this for the whole pytest process before any test
    spawns a factory subprocess, so no test — present or future — can reach
    the real botmaster/Telegram channel. Unset (or any other value) means the
    real default: on.
    """
    return os.environ.get("FACTORY_NOTIFY", "").strip().lower() in _OFF_VALUES


def _send(message: str) -> None:
    if _disabled():
        return
    try:
        result = subprocess.run(
            ["botmaster", "--channel", CHANNEL, message],
            capture_output=True, text=True, timeout=SEND_TIMEOUT_S,
        )
        if result.returncode != 0:
            print(f"factory notify: botmaster exited {result.returncode}: "
                  f"{result.stderr.strip()[:300]}", file=sys.stderr)
    except FileNotFoundError:
        print("factory notify: botmaster not on PATH — skipping notification", file=sys.stderr)
    except Exception as error:  # noqa: BLE001 - fail-open by design
        print(f"factory notify: {error}", file=sys.stderr)


def _label(adw_id: str, adw_name: str | None, slug_hint: str | None) -> str:
    bits = [b for b in (adw_name, slug_hint) if b]
    if not bits:
        return adw_id
    return f"{' '.join(bits)} ({adw_id})"


def notify_start(adw_id: str, adw_name: str | None, slug_hint: str | None) -> None:
    _send(f"factory started: {_label(adw_id, adw_name, slug_hint)}")


def notify_finish(adw_id: str, adw_name: str | None, ok: bool, duration_s: float | None) -> None:
    status = "passed" if ok else "failed"
    dur = f", {duration_s:.0f}s" if duration_s is not None else ""
    _send(f"factory finished: {_label(adw_id, adw_name, None)} — {status}{dur}")


def notify_crash(adw_id: str, adw_name: str | None, duration_s: float | None = None,
                 detail: str | None = None) -> None:
    """One CRASHED message, owner language only.

    `detail`, when given, MUST be a fixed, caller-controlled phrase (e.g. "run
    vanished without finishing") — never raw exception text, a phase name, or
    a log path pulled from a failure dump. Those are internal jargon and stay
    out of the owner-facing channel; a log path belongs on its own trailing
    line only if the caller has one worth surfacing.
    """
    dur = f", {duration_s:.0f}s" if duration_s is not None else ""
    message = f"factory CRASHED: {_label(adw_id, adw_name, None)} — failed{dur}"
    if detail:
        message += f"\n{detail}"
    _send(message)
