"""Telemetry, logging, and notification utilities for kintsugi."""

from __future__ import annotations

import json
import logging
import os
import socket as _socket
from datetime import UTC, datetime

from ._helpers import (
    _KINTSUGI_LOG_DIR,
    _KINTSUGI_NDJSON,
    _get_worker_cache,
    _is_transient_error,
    _kintsugi_cfg,
)

logger = logging.getLogger(__name__)


def _emit_telemetry(event: str, **fields: object) -> None:
    """Append structured NDJSON telemetry line to kintsugi.ndjson.

    Persists independently of ClickHouse — survives tunnel drops.
    Rotates at 10 MB, keeping one backup (.ndjson.1).
    """
    try:
        _KINTSUGI_LOG_DIR.mkdir(parents=True, exist_ok=True)
        # Rotate if >10 MB
        if (
            _KINTSUGI_NDJSON.exists()
            and _KINTSUGI_NDJSON.stat().st_size > _kintsugi_cfg().ndjson_max_bytes
        ):
            rotated = _KINTSUGI_NDJSON.with_suffix(".ndjson.1")
            rotated.unlink(missing_ok=True)
            _KINTSUGI_NDJSON.rename(rotated)
        record = {
            "ts": datetime.now(UTC).isoformat(),
            "pid": os.getpid(),
            "event": event,
            **fields,
        }
        with _KINTSUGI_NDJSON.open("a") as f:
            f.write(json.dumps(record, default=str) + "\n")
    except OSError:
        pass  # Best-effort — never crash kintsugi for telemetry


def _sd_notify(state: str) -> None:
    """Send sd_notify message to systemd (if NOTIFY_SOCKET is set).

    This is a minimal implementation — no external dependency needed.
    Used for WatchdogSec integration (Issue #117).
    """
    addr = os.environ.get("NOTIFY_SOCKET")
    if not addr:
        return
    try:
        sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_DGRAM)
        try:
            if addr.startswith("@"):
                addr = "\0" + addr[1:]
            sock.sendto(state.encode(), addr)
        finally:
            sock.close()
    except OSError:
        logger.debug("sd_notify(%s) failed", state, exc_info=True)


def _maybe_log_repair(result: object) -> None:
    """Log repair result, skipping kintsugi_log for transient/deferred errors.

    Transient errors (network, timeout, 5xx) and today-guard deferrals are not
    written to the kintsugi_log table. Transient errors don't count toward the
    3-attempt-per-24h rate limit. Deferred shards must not be dismissed — they
    were skipped because the sidecar is actively writing, not because the gap
    is sub-threshold.
    """
    # Import here to avoid circular imports — result is KintsugiResult
    from .discovery import KintsugiResult

    assert isinstance(result, KintsugiResult)

    if result.error == "deferred_today_guard":
        return
    if result.error and _is_transient_error(result.error):
        logger.info(
            "transient error for %s@%d — not counting toward attempt limit: %s",
            result.shard.symbol,
            result.shard.threshold_decimal_bps,
            result.error,
        )
        return
    _log_repair(result)


def _log_repair(result: object) -> None:
    """Write repair result to kintsugi_log ClickHouse table."""
    from .discovery import KintsugiResult

    assert isinstance(result, KintsugiResult)

    try:
        from ._helpers import _get_mode_for_symbol

        cache = _get_worker_cache()
        cache.client.command(
            "INSERT INTO opendeviationbar_cache.kintsugi_log "
            "(symbol, threshold_decimal_bps, priority, "
            "gap_start_ms, gap_end_ms, gap_hours, "
            "action, result, ariadne_from_id, ouroboros_splits, "
            "bars_written, duration_seconds, error_message, ouroboros_mode) "
            "VALUES "
            "({symbol:String}, {threshold:UInt32}, {priority:String}, "
            "{gap_start:Int64}, {gap_end:Int64}, {gap_hours:Float64}, "
            "{action:String}, {result:String}, {ariadne:Int64}, "
            "{splits:UInt8}, {bars:UInt32}, {duration:Float64}, "
            "{error:String}, {ouroboros_mode:String})",
            parameters={
                "symbol": result.shard.symbol,
                "threshold": result.shard.threshold_decimal_bps,
                "priority": "",
                "gap_start": result.shard.gap_start_ms,
                "gap_end": result.shard.gap_end_ms,
                "gap_hours": result.shard.gap_hours,
                "action": "backfill_gap",
                "result": (
                    "exchange_gap"
                    if getattr(result, "is_exchange_gap", False)
                    else "healed"
                    if result.healed
                    else "clean"
                    if not result.error
                    else "failed"
                ),
                "ariadne": (result.shard.anchor_last_tid or 0),  # column name legacy; stores anchor_last_tid for audit trail
                "splits": result.ouroboros_splits,
                "bars": result.bars_written,
                "duration": result.duration_seconds,
                "error": result.error or "",
                "ouroboros_mode": _get_mode_for_symbol(result.shard.symbol),
            },
        )
    except (OSError, RuntimeError, ConnectionError) as e:
        logger.warning("failed to log kintsugi result: %s", e)


def _notify_kintsugi_failures(
    results: list[object],
    pass_duration: float = 0.0,
) -> None:
    """Send LOUD Telegram alert for failed Stathera repairs.

    Stathera-only: reports trade-ID anchors and missing trade counts.
    """
    from .discovery import KintsugiResult

    try:
        from opendeviationbar.notify.telegram import _build_context_block, send_telegram
    except ImportError:
        return

    failed = [r for r in results if isinstance(r, KintsugiResult) and r.error and r.error != "deferred_today_guard"]
    if not failed:
        return

    healed_count = sum(1 for r in results if isinstance(r, KintsugiResult) and r.healed)

    lines = [
        f"Kintsugi failed to repair {len(failed)} anomalies\n",
        f"This pass: {healed_count} repaired, {len(failed)} failed "
        f"({pass_duration:.0f}s)",
    ]
    for r in failed[:_kintsugi_cfg().max_failures_in_alert]:
        anomaly = "overlap" if r.shard.is_overlap else "gap"
        lines.append(
            f"\n  {r.shard.symbol} @ {r.shard.threshold_decimal_bps} dbps — "
            f"{anomaly} ({r.shard.missing_trade_count:,} trades)\n"
            f"  Reason: <code>{r.error or 'unknown'}</code>"
        )
    if len(failed) > _kintsugi_cfg().max_failures_in_alert:
        lines.append(f"\n  ... and {len(failed) - _kintsugi_cfg().max_failures_in_alert} more")
    lines.append("\nWill retry automatically (max 3 attempts per shard per day).")
    lines.append(
        "If this keeps happening, check: Binance Vision data availability, "
        "REST rate limits, ClickHouse write health."
    )
    lines.append(_build_context_block("kintsugi"))

    try:
        send_telegram("\n".join(lines), disable_notification=False)
    except (OSError, RuntimeError, ConnectionError):
        logger.exception("failed to send kintsugi failure alert")
