#!/usr/bin/env bash
# welcomebot-reputation — inspect the pre-signup reputation query API's activity.
#
# Installed at /usr/local/bin/welcomebot-reputation on admin.yttrx.com.
# Source of truth: ~/yttrx-welcomebot/bin/welcomebot-reputation (workstation).
#
# Reads the persistent `reputation_query` (every answer the API gave) and
# `reputation_block` (every block it installed) sqlite tables, so this survives
# log rotation — see app/main.py's reputation_check / README's "Reputation
# query API". For the raw log lines instead, use `welcomebot-logs --reputation`.
set -euo pipefail

CONTAINER="${WELCOMEBOT_CONTAINER:-yttrx-welcomebot}"
limit=20
mode="queries"
filter="all"
kind_filter=""
since=""

usage() {
  cat <<EOF
Usage: welcomebot-reputation [options]

Show what the pre-signup reputation API has been asked and what it blocked,
newest first. This is the pre-emptive path (Mastodon asking before it creates
an account); for the reactive signup path use welcomebot-signups.

Options:
  -n, --limit N     Show the last N rows (default: $limit; 'all' for everything).
      --blocks      List installed blocks instead of queries (what is in force).
      --stats       Summary: volume, flag/block rate, cache hit rate, deferrals.
      --flagged     Queries only: just the ones that came back flagged.
      --blocked     Queries only: just the ones that installed or hit a block.
      --deferred    Queries only: just the ones refused by the hourly budget.
      --ip          Only the IP side.
      --email       Only the email-domain side.
      --since WHEN  Only rows newer than WHEN (SQL datetime, e.g. '2026-09-13'
                    or '-24 hours').
  -h, --help        Show this help.

Examples:
  welcomebot-reputation                    # last $limit answers
  welcomebot-reputation --stats            # is it earning its keep?
  welcomebot-reputation --blocks           # what it currently has blocked
  welcomebot-reputation --blocked --since '-24 hours'
  welcomebot-reputation --deferred         # did the budget ceiling bite?
EOF
}

while [ $# -gt 0 ]; do
  case "$1" in
    -n|--limit) shift; limit="${1:?--limit needs a value}" ;;
    --blocks)   mode="blocks" ;;
    --stats)    mode="stats" ;;
    --flagged)  filter="flagged" ;;
    --blocked)  filter="blocked" ;;
    --deferred) filter="deferred" ;;
    --ip)       kind_filter="ip" ;;
    --email)    kind_filter="email" ;;
    --since)    shift; since="${1:?--since needs a value}" ;;
    -h|--help)  usage; exit 0 ;;
    *) echo "welcomebot-reputation: unknown option: $1" >&2; usage >&2; exit 2 ;;
  esac
  shift
done

if ! command -v docker >/dev/null 2>&1; then
  echo "welcomebot-reputation: docker not found in PATH" >&2; exit 1
fi
if ! docker inspect "$CONTAINER" >/dev/null 2>&1; then
  echo "welcomebot-reputation: container '$CONTAINER' not found (is it deployed/running?)" >&2; exit 1
fi

docker exec -i \
  -e REP_LIMIT="$limit" \
  -e REP_MODE="$mode" \
  -e REP_FILTER="$filter" \
  -e REP_KIND="$kind_filter" \
  -e REP_SINCE="$since" \
  "$CONTAINER" python3 <<'PYEOF'
import os
import sqlite3

conn = sqlite3.connect("/data/welcomed.db")
mode = os.environ.get("REP_MODE", "queries")
kind = os.environ.get("REP_KIND", "")
since = os.environ.get("REP_SINCE", "")
limit = os.environ.get("REP_LIMIT", "20")


def limited(sql):
    return sql if limit == "all" else f"{sql} LIMIT {int(limit)}"


def since_expr(column):
    """SQL for the --since bound.

    A leading +/- means "relative to now" — sqlite needs that spelled
    datetime('now', '-24 hours'), NOT datetime('-24 hours'), which returns
    NULL and silently matches no rows.
    """
    if since.startswith(("+", "-")):
        return f"{column} >= datetime('now', ?)"
    return f"{column} >= datetime(?)"


def tables_exist():
    names = {r[0] for r in conn.execute(
        "SELECT name FROM sqlite_master WHERE type = 'table'")}
    return {"reputation_query", "reputation_block"} <= names


if not tables_exist():
    print("(no reputation tables yet — the query API has never been called on "
          "this deployment)")
    raise SystemExit(0)

if mode == "stats":
    where, params = [], []
    if since:
        where.append(since_expr("queried_at"))
        params.append(since)
    clause = (" WHERE " + " AND ".join(where)) if where else ""
    row = conn.execute(
        "SELECT COUNT(*),"
        "       SUM(flagged), SUM(blocked),"
        "       SUM(CASE WHEN classification = 'unknown' THEN 1 ELSE 0 END),"
        "       MIN(queried_at), MAX(queried_at)"
        f" FROM reputation_query{clause}", params).fetchone()
    total, flagged, blocked, unknown, first, last = row
    total = total or 0
    if not total:
        print("(no queries in range)")
        raise SystemExit(0)

    def pct(n):
        return f"{(n or 0) / total * 100:5.1f}%"

    print(f"queries          {total:>7}   {first} .. {last}")
    print(f"  flagged        {flagged or 0:>7}   {pct(flagged)}")
    print(f"  led to a block {blocked or 0:>7}   {pct(blocked)}")
    print(f"  unknown        {unknown or 0:>7}   {pct(unknown)}  "
          "(lookup failed, or refused by the hourly budget)")
    print()
    print("by source:")
    for source, n, nb in conn.execute(
            "SELECT COALESCE(source, '-'), COUNT(*), SUM(blocked) "
            f"FROM reputation_query{clause} GROUP BY 1 ORDER BY 2 DESC", params):
        print(f"  {source:24s} {n:>7} queries, {nb or 0:>5} blocked")
    print()
    print("blocks installed (still in force):")
    for kind_, n in conn.execute(
            "SELECT kind, COUNT(*) FROM reputation_block "
            "WHERE block_active = 1 AND (expires_at IS NULL OR expires_at > "
            "  strftime('%Y-%m-%dT%H:%M:%S+00:00', 'now')) GROUP BY 1"):
        print(f"  {kind_:24s} {n:>7}")
    raise SystemExit(0)

if mode == "blocks":
    where, params = [], []
    if kind:
        where.append("kind = ?")
        params.append(kind)
    if since:
        where.append(since_expr("first_blocked_at"))
        params.append(since)
    clause = (" WHERE " + " AND ".join(where)) if where else ""
    rows = conn.execute(limited(
        "SELECT kind, target, classification, org, queries, first_blocked_at,"
        "       last_seen_at, block_active, expires_at"
        f" FROM reputation_block{clause} ORDER BY last_seen_at DESC"), params).fetchall()
    if not rows:
        print("(no blocks installed by the query API)")
    for k, target, cls, org, queries, first, last, active, expires in rows:
        state = "active" if active else "GONE"
        exp = f"expires {expires}" if expires else "no expiry"
        print(f"{last}  {k:6s} {target:22s} {state:7s} {exp:34s} "
              f"hits={queries:<5} {cls or ''} {org or ''}".rstrip())
    raise SystemExit(0)

# mode == "queries"
where, params = [], []
filt = os.environ.get("REP_FILTER", "all")
if filt == "flagged":
    where.append("flagged = 1")
elif filt == "blocked":
    where.append("blocked = 1")
elif filt == "deferred":
    where.append("classification = 'unknown'")
if kind:
    where.append("kind = ?")
    params.append(kind)
if since:
    where.append(since_expr("queried_at"))
    params.append(since)
clause = (" WHERE " + " AND ".join(where)) if where else ""

rows = conn.execute(limited(
    "SELECT queried_at, kind, value, classification, org, risk, flagged,"
    "       target, blocked, source"
    f" FROM reputation_query{clause} ORDER BY id DESC"), params).fetchall()
if not rows:
    print("(no matching reputation queries)")
for ts, k, value, cls, org, risk, flagged, target, blocked, source in rows:
    flag = "FLAGGED" if flagged else "-"
    blk = "BLOCKED" if blocked else "-"
    extra = org or ""
    if k == "email" and risk:
        extra = f"risk={risk}"
    print(f"{ts}  {k:6s} {value or '':24s} {cls or '':22s} {flag:8s} {blk:8s} "
          f"{target or '':22s} {source or '-':16s} {extra}".rstrip())
PYEOF
