"""One-shot status snapshot of the nostr ingest daemon.

DuckDB's single-writer model means even read-only opens fail while the
daemon holds the file lock, so we snapshot `nostr.duckdb` (+ its `.wal`,
if present) into a temp dir and read from the copy. The snapshot may
trail the live state by a few hundred ms, which is fine for monitoring.

Tail of `mcp_admin.log` provides live-rate signals.

    ./.venv/bin/python monitor_ingest.py

Override paths via env: NOSTR_DB / NOSTR_ADMIN_LOG.
"""

from __future__ import annotations

import os
import re
import shutil
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path

import duckdb

DEFAULT_DB = Path(__file__).resolve().parents[2] / "duckdb" / "nostr.duckdb"
DEFAULT_LOG = Path(__file__).resolve().parent / "mcp_admin.log"

LOG_TAIL_BYTES = 2 * 1024 * 1024  # ~2 MB suffices for a few hours of logs
RECENT_WINDOW_MIN = 60

PERSIST_RE = re.compile(
    r"^(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),\d+ "
    r"ingest_sql INFO persist [0-9a-f]+: requested=(?P<role>primary|secondary)"
)
TRACK_START_RE = re.compile(
    r"^(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),\d+ "
    r"track_pubkey INFO track_pubkey\.run "
)
AUTO_START_RE = re.compile(
    r"^(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}),\d+ "
    r"ingest_sql INFO auto-tracker started"
)


def _fmt_dt(unix_s: int | None) -> str:
    if not unix_s:
        return "—"
    return datetime.fromtimestamp(unix_s, tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")


def _fmt_int(n: int | None) -> str:
    return f"{n:>7,}" if n is not None else "      —"


def _section(title: str) -> None:
    print()
    print(f"── {title} " + "─" * (60 - len(title)))


def _read_log_tail(path: Path) -> list[str]:
    if not path.exists():
        return []
    size = path.stat().st_size
    with path.open("rb") as f:
        if size > LOG_TAIL_BYTES:
            f.seek(size - LOG_TAIL_BYTES)
            f.readline()  # discard partial first line
        return f.read().decode("utf-8", errors="replace").splitlines()


def _parse_log_ts(s: str) -> datetime | None:
    try:
        return datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
    except ValueError:
        return None


def _log_summary(lines: list[str], window_min: int) -> dict:
    now = datetime.now(timezone.utc)
    cutoff = now.timestamp() - window_min * 60

    persists_window = {"primary": 0, "secondary": 0}
    persists_window_ts: list[float] = []
    last_persist_line: str | None = None
    last_track_start_line: str | None = None
    last_auto_start_line: str | None = None

    for line in lines:
        m = PERSIST_RE.match(line)
        if m:
            ts = _parse_log_ts(m.group("ts"))
            if ts is not None and ts.timestamp() >= cutoff:
                persists_window[m.group("role")] += 1
                persists_window_ts.append(ts.timestamp())
            last_persist_line = line
            continue
        m = TRACK_START_RE.match(line)
        if m:
            last_track_start_line = line
            continue
        m = AUTO_START_RE.match(line)
        if m:
            last_auto_start_line = line

    avg_per_pubkey_s = None
    if len(persists_window_ts) >= 2:
        span = persists_window_ts[-1] - persists_window_ts[0]
        gaps = len(persists_window_ts) - 1
        if gaps > 0:
            avg_per_pubkey_s = span / gaps

    return {
        "persists_window": persists_window,
        "avg_per_pubkey_s": avg_per_pubkey_s,
        "last_persist_line": last_persist_line,
        "last_track_start_line": last_track_start_line,
        "last_auto_start_line": last_auto_start_line,
    }


def main() -> int:
    db_path = Path(os.environ.get("NOSTR_DB", str(DEFAULT_DB)))
    log_path = Path(os.environ.get("NOSTR_ADMIN_LOG", str(DEFAULT_LOG)))

    if not db_path.exists():
        print(f"ERROR: db not found: {db_path}", file=sys.stderr)
        return 2

    print(f"DB:  {db_path}")
    print(f"LOG: {log_path}{'' if log_path.exists() else '  (missing — log section skipped)'}")
    print(f"NOW: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}")

    with tempfile.TemporaryDirectory(prefix="nostr-monitor-") as td:
        snapshot = Path(td) / db_path.name
        shutil.copy2(db_path, snapshot)
        wal = db_path.with_suffix(db_path.suffix + ".wal")
        if wal.exists():
            shutil.copy2(wal, snapshot.with_suffix(snapshot.suffix + ".wal"))
        con = duckdb.connect(str(snapshot), read_only=True)
        return _report(con, log_path)


def _report(con: "duckdb.DuckDBPyConnection", log_path: Path) -> int:

    _section("tracked_pubkeys")
    rows = con.execute(
        """
        SELECT
          role,
          COUNT(*)                                                          AS total,
          COUNT(*) FILTER (WHERE last_scan_at IS NULL)                      AS unscanned,
          COUNT(*) FILTER (WHERE last_scan_at IS NOT NULL)                  AS scanned,
          COUNT(*) FILTER (WHERE productive_relays IS NOT NULL
                                 AND len(productive_relays) > 0)            AS with_productive
        FROM tracked_pubkeys
        GROUP BY role
        ORDER BY role
        """
    ).fetchall()
    print(f"  {'role':<10} {'total':>8} {'unscanned':>10} {'scanned':>9} {'productive':>11}")
    for role, total, unscanned, scanned, prod in rows:
        print(f"  {role:<10} {_fmt_int(total)} {_fmt_int(unscanned)} {_fmt_int(scanned)} {_fmt_int(prod)}")

    _section("auto-tracker queue (last_scan_at IS NULL, ordered as the daemon claims them)")
    rows = con.execute(
        """
        SELECT
          role,
          COUNT(*) AS waiting
        FROM tracked_pubkeys
        WHERE last_scan_at IS NULL
        GROUP BY role
        ORDER BY (role = 'primary') DESC, role
        """
    ).fetchall()
    if rows:
        for role, waiting in rows:
            print(f"  {role:<10} {_fmt_int(waiting)}")
    else:
        print("  (queue empty)")

    next_up = con.execute(
        """
        SELECT role, hex, added_at
        FROM tracked_pubkeys
        WHERE last_scan_at IS NULL
        ORDER BY (role = 'primary') DESC, added_at
        LIMIT 5
        """
    ).fetchall()
    if next_up:
        print()
        print("  next up:")
        for role, hex_, added_at in next_up:
            print(f"    {role:<10} {hex_[:8]}…  added {_fmt_dt(added_at)}")

    _section("events by kind (top 12)")
    rows = con.execute(
        """
        SELECT kind, COUNT(*) AS n
        FROM events
        GROUP BY kind
        ORDER BY n DESC
        LIMIT 12
        """
    ).fetchall()
    print(f"  {'kind':>6} {'count':>10}")
    total_events = 0
    for kind, n in rows:
        total_events += n
        kind_label = "NULL" if kind is None else str(kind)
        print(f"  {kind_label:>6} {_fmt_int(n)}")
    grand_total = con.execute("SELECT COUNT(*) FROM events").fetchone()[0]
    print(f"  {'(top12)':>6} {_fmt_int(total_events)}   ·   total: {_fmt_int(grand_total)}")

    _section("freshness")
    row = con.execute(
        """
        SELECT
          MAX(created_at) AS newest,
          MIN(created_at) FILTER (WHERE created_at > 0) AS oldest
        FROM events
        """
    ).fetchone()
    newest, oldest = row
    if newest:
        print(f"  newest event ts: {_fmt_dt(newest)}")
        if oldest:
            print(f"  oldest event ts: {_fmt_dt(oldest)}")
    else:
        print("  (no events)")

    last_scan_row = con.execute(
        """
        SELECT MAX(last_scan_at), MIN(last_scan_at)
        FROM tracked_pubkeys
        WHERE last_scan_at IS NOT NULL
        """
    ).fetchone()
    if last_scan_row[0]:
        print(f"  newest scan stamp: {_fmt_dt(last_scan_row[0])}")
        print(f"  oldest scan stamp: {_fmt_dt(last_scan_row[1])}")

    _section("relays")
    relays = con.execute(
        """
        SELECT
          COUNT(*) FILTER (WHERE COALESCE(active, true))                AS active,
          COUNT(*) FILTER (WHERE status = 'ok')                          AS status_ok,
          COUNT(*) FILTER (WHERE status = 'error')                       AS status_error,
          COUNT(*)                                                       AS total
        FROM relays
        """
    ).fetchone()
    active, status_ok, status_error, total = relays
    print(f"  registered: {total}   active: {active}   status=ok: {status_ok}   status=error: {status_error}")

    if log_path.exists():
        _section(f"recent log activity (last {RECENT_WINDOW_MIN} min)")
        summary = _log_summary(_read_log_tail(log_path), RECENT_WINDOW_MIN)
        pw = summary["persists_window"]
        total_persists = pw["primary"] + pw["secondary"]
        print(f"  persists in window:  primary={pw['primary']}  secondary={pw['secondary']}  total={total_persists}")
        if summary["avg_per_pubkey_s"] is not None:
            avg = summary["avg_per_pubkey_s"]
            print(f"  avg gap between persists: {avg:.1f} s ({60/avg:.1f}/min)")

        for label, line in (
            ("last auto-tracker start", summary["last_auto_start_line"]),
            ("last track_pubkey.run",   summary["last_track_start_line"]),
            ("last persist",            summary["last_persist_line"]),
        ):
            if line:
                print(f"  {label}:")
                print(f"    {line.strip()}")

    print()
    return 0


if __name__ == "__main__":
    sys.exit(main())
