# RESUME-03: Junction telemetry at REST-to-WS handoff
"""Junction telemetry logging for sidecar startup.

After fill_from_rest and sync_flush complete, before engine.start() begins
WS streaming, this module measures the quality of the handoff per symbol:
ws_buffer_depth, parquet_coverage, and enriches _junction_state for the
/status endpoint.

Non-fatal: any failure is caught and logged as a warning.
"""

from __future__ import annotations

import json
import logging
from datetime import UTC, datetime
from typing import Protocol

from opendeviationbar.logging import NDJSON_FILE

from .http_server import _junction_state


class _HasMetrics(Protocol):
    """Protocol for engine objects that expose get_metrics()."""

    def get_metrics(self) -> dict[str, int]: ...

logger = logging.getLogger("opendeviationbar.sidecar")


def _log_junction_telemetry(
    engine: _HasMetrics,
    aion_symbols: list[str],
    resume_tids: dict[str, int | None],
) -> None:
    """Log per-symbol handoff quality metrics at REST-to-WS junction.

    Parameters
    ----------
    engine
        StreamManager (or OdbEngine) with ``get_metrics()`` method.
    aion_symbols
        Symbols to report on.
    resume_tids
        Original resume TIDs before Parquet prefill (symbol -> tid or None).
    """
    try:
        _log_junction_telemetry_inner(engine, aion_symbols, resume_tids)
    except (OSError, RuntimeError, ValueError, KeyError, TypeError, ConnectionError):
        logger.warning("junction telemetry failed (non-fatal)", exc_info=True)


def _log_junction_telemetry_inner(
    engine: _HasMetrics,
    aion_symbols: list[str],
    resume_tids: dict[str, int | None],
) -> None:
    """Inner implementation (unwrapped for testability)."""
    # Get engine metrics for ws_buffer_depth
    metrics = engine.get_metrics()
    ws_buffer_depth = metrics.get("trades_received", 0)

    now_iso = datetime.now(UTC).isoformat()

    for symbol in aion_symbols:
        sym_data = _junction_state.get(symbol)

        if sym_data is None:
            # No REST data for this symbol -- mark and continue
            _junction_state[symbol] = {
                "status": "no_rest_data",
                "junction_logged_at": now_iso,
                "ws_buffer_depth": ws_buffer_depth,
                "parquet_coverage": False,
            }
            logger.info(
                "junction_telemetry: %s no_rest_data ws_buffer_depth=%d",
                symbol,
                ws_buffer_depth,
            )
            continue

        rest_last_tid = sym_data.get("rest_last_tid")
        if rest_last_tid is None:
            _junction_state[symbol]["status"] = "no_rest_data"
            _junction_state[symbol]["junction_logged_at"] = now_iso
            _junction_state[symbol]["ws_buffer_depth"] = ws_buffer_depth
            _junction_state[symbol]["parquet_coverage"] = False
            logger.info(
                "junction_telemetry: %s no_rest_data (no rest_last_tid) ws_buffer_depth=%d",
                symbol,
                ws_buffer_depth,
            )
            continue

        resume_tid = resume_tids.get(symbol)
        parquet_coverage = (
            rest_last_tid > resume_tid if resume_tid is not None else False
        )

        # Enrich _junction_state for /status endpoint
        sym_data["junction_logged_at"] = now_iso
        sym_data["ws_buffer_depth"] = ws_buffer_depth
        sym_data["parquet_coverage"] = parquet_coverage

        logger.info(
            "junction_telemetry: %s rest_last_tid=%d ws_buffer_depth=%d resume_tid=%s parquet_advanced=%s",
            symbol,
            rest_last_tid,
            ws_buffer_depth,
            resume_tid,
            parquet_coverage,
        )

        # Write NDJSON entry to local telemetry log
        event = {
            "event": "junction_telemetry",
            "timestamp": now_iso,
            "symbol": symbol,
            "rest_last_tid": rest_last_tid,
            "rest_first_tid": sym_data.get("rest_first_tid"),
            "ws_buffer_depth": ws_buffer_depth,
            "resume_tid": resume_tid,
            "parquet_coverage": parquet_coverage,
        }
        try:
            NDJSON_FILE.parent.mkdir(parents=True, exist_ok=True)
            with NDJSON_FILE.open("a") as f:
                f.write(json.dumps(event) + "\n")
        except OSError:
            logger.debug("junction telemetry: failed to write NDJSON (non-fatal)", exc_info=True)
