#!/bin/bash
# trendwatch: трендвотчинг референс-каналов → разбор headless Claude → телеграм.
# Режим по дню недели: вс — полный дайджест за 7 дней (+файлы, вкл. EN-неделю),
# остальные дни — сигналы за 2 дня (тихо → ничего не шлём, Claude не запускаем).
# Кроссплатформенный (Mac launchd / Pi systemd): пути из .env —
#   TGSTAT_VAULT_ROOT (обязателен), TRENDWATCH_DATA_DIR (логи/состояние, опц.),
#   TRENDWATCH_GIT_SYNC=1 (git pull/push волта вокруг прогона — для Pi).
# Запуск: bash trendwatch.sh [auto|full|signals] [days]
set -u

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

# --- секреты и пути
set -a; source "$SCRIPT_DIR/.env"; set +a
VAULT="${TGSTAT_VAULT_ROOT:?В .env нет TGSTAT_VAULT_ROOT}"
LOG_DIR="${TRENDWATCH_DATA_DIR:-$HOME/Library/Application Support/tgstat-puller}"
LOG="$LOG_DIR/trendwatch.log"
CLAUDE_BIN="${CLAUDE_BIN:-$(command -v claude)}"
mkdir -p "$LOG_DIR"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG"; }

if [ -z "${TELEGRAM_BOT_TOKEN:-}" ] || [ -z "${TELEGRAM_OWNER_USER_ID:-}" ]; then
  log "ERROR: нет токена/chat_id в .env"; exit 1
fi

# --- режим
MODE="${1:-auto}"
DAYS="${2:-}"
if [ "$MODE" = "auto" ]; then
  if [ "$(date +%u)" = "7" ]; then MODE="full"; else MODE="signals"; fi
fi
if [ -z "$DAYS" ]; then
  if [ "$MODE" = "full" ]; then DAYS=7; else DAYS=2; fi
fi

# Модель по режиму: воскресный дайджест — анализ (opus), будничные сигналы — форматирование (haiku)
if [ "$MODE" = "full" ]; then CLAUDE_MODEL="claude-opus-5"; else CLAUDE_MODEL="claude-haiku-4-5-20251001"; fi

log "start: режим $MODE, окно $DAYS дн."

# --- учёт квоты TGStat (2 запроса на прогон: замер до и после).
# По тому же токену ходят pull.py на Маке и на Selectel, поэтому в лог пишем
# и то, сколько съел сам прогон, и то, сколько между прогонами съели остальные.
QUOTA_STATE="$LOG_DIR/quota-last.txt"

quota_now() {  # «потрачено лимит каналы дней_до_сброса» активного периода, или пусто
  curl -s --max-time 15 "https://api.tgstat.ru/usage/stat?token=${TGSTAT_TOKEN:-}" 2>/dev/null \
    | python3 -c '
import json, sys, time
try:
    rows = json.load(sys.stdin).get("response") or []
except Exception:
    sys.exit(0)
now = time.time()
live = [r for r in rows if (r.get("expiredAt") or 0) > now]   # истёкшие периоды не считаем
if not live:
    sys.exit(0)
r = max(live, key=lambda x: x["expiredAt"])
spent, limit = (r.get("spentRequests") or "0/0").split("/")
print(spent, limit, r.get("spentChannels", "?"), int((r["expiredAt"] - now) // 86400), int(r["expiredAt"]))
' 2>/dev/null
}

Q_RAW=$(quota_now)
QUOTA_BEFORE=$(echo "$Q_RAW" | cut -d' ' -f1)
Q_LIMIT=$(echo "$Q_RAW" | cut -d' ' -f2)
Q_CHANNELS=$(echo "$Q_RAW" | cut -d' ' -f3)
Q_DAYS=$(echo "$Q_RAW" | cut -d' ' -f4)
Q_PERIOD=$(echo "$Q_RAW" | cut -d' ' -f5)

if [ -n "$QUOTA_BEFORE" ]; then
  BUDGET=$(( (Q_LIMIT - QUOTA_BEFORE) / (Q_DAYS > 0 ? Q_DAYS : 1) ))
  log "квота до прогона: $QUOTA_BEFORE/$Q_LIMIT, каналов $Q_CHANNELS, до сброса ${Q_DAYS} дн. — бюджет $BUDGET запр./день"
  if [ -f "$QUOTA_STATE" ]; then
    PREV=$(cat "$QUOTA_STATE" 2>/dev/null)
    # после сброса периода счётчик падает — отрицательную дельту не логируем
    if [ -n "$PREV" ] && [ "$QUOTA_BEFORE" -ge "$PREV" ]; then
      log "с прошлого прогона другие потребители токена съели $((QUOTA_BEFORE - PREV))"
    fi
  fi
fi

QUOTA_WARNED="$LOG_DIR/quota-warned.txt"   # метка периода, за который уже предупреждали

quota_report() {  # вызывается на любом выходе, в том числе на тихом дне и на ошибке
  [ -z "$QUOTA_BEFORE" ] && return 0
  local after prev daily left days_left warned
  after=$(quota_now | cut -d' ' -f1)
  [ -z "$after" ] && return 0
  log "квота после: $after/$Q_LIMIT — прогон съел $((after - QUOTA_BEFORE))"

  prev=""
  [ -f "$QUOTA_STATE" ] && prev=$(cat "$QUOTA_STATE" 2>/dev/null)
  echo "$after" > "$QUOTA_STATE"

  # --- прогноз: хватит ли остатка до сброса лимита при текущем суточном расходе.
  # Молчим, если это первый прогон (не с чем сравнить) или период только что сбросился.
  [ -z "$prev" ] && return 0
  [ "$after" -lt "$prev" ] && return 0
  daily=$((after - prev))
  [ "$daily" -le 0 ] && return 0
  left=$((Q_LIMIT - after))
  days_left=$((left / daily))
  [ "$days_left" -gt "$Q_DAYS" ] && return 0        # хватает до сброса — всё хорошо

  # предупреждаем один раз за период, иначе будет капать каждый день до сброса
  warned=""
  [ -f "$QUOTA_WARNED" ] && warned=$(cat "$QUOTA_WARNED" 2>/dev/null)
  [ "$warned" = "$Q_PERIOD" ] && return 0
  echo "$Q_PERIOD" > "$QUOTA_WARNED"

  log "ПРЕДУПРЕЖДЕНИЕ: остатка $left хватит на $days_left дн., до сброса $Q_DAYS дн."
  curl -s -o /dev/null -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
    --data-urlencode "chat_id=$TELEGRAM_OWNER_USER_ID" \
    --data-urlencode "text=⚠️ TGStat: квота на исходе.

Потрачено $after из $Q_LIMIT запросов. Расход ~$daily в день — остатка хватит примерно на $days_left дн., а лимит сбросится только через $Q_DAYS дн.

Если упрёмся, трендвотчинг просто замолчит (тихий день у него штатный, по молчанию не отличишь). Чтобы дотянуть — снизить потолки posts/stat или гонять биз-трек не каждый день, а по средам и воскресеньям."
}
trap quota_report EXIT

# --- на Pi: подтянуть свежий волт перед прогоном
if [ "${TRENDWATCH_GIT_SYNC:-0}" = "1" ]; then
  # rebase --abort обязателен: без него неудавшийся rebase оставляет клон в
  # полуразобранном состоянии, и следом падают все, кто тянет волт после.
  if ! git -C "$VAULT" pull --rebase >> "$LOG" 2>&1; then
    git -C "$VAULT" rebase --abort >> "$LOG" 2>&1
    log "WARN: git pull --rebase не прошёл — откатил rebase, работаю на локальном состоянии волта"
  fi
fi

# --- сбор
EXTRA=""
[ "$MODE" = "signals" ] && EXTRA="--signals --defer-state"
COLLECT_OUT=$("$SCRIPT_DIR/venv/bin/python" "$SCRIPT_DIR/watch.py" --days "$DAYS" $EXTRA 2>>"$LOG")
RC=$?
DIGEST=$(echo "$COLLECT_OUT" | grep '^DIGEST=' | cut -d= -f2-)
HAS_SIGNALS=$(echo "$COLLECT_OUT" | grep '^HAS_SIGNALS=' | cut -d= -f2-)
STATE_PENDING=$(echo "$COLLECT_OUT" | grep '^STATE_PENDING=' | cut -d= -f2-)
if [ $RC -ne 0 ] || [ -z "$DIGEST" ] || [ ! -f "$DIGEST" ]; then
  log "ERROR: сбор не удался (rc=$RC, digest='$DIGEST')"
  exit 1
fi
log "digest: $DIGEST"

# --- второй трек: каналы предпринимателей (тот же режим/окно, отдельный лейбл)
BIZ_DIGEST=""
BIZ_HAS_SIGNALS="0"
BIZ_STATE_PENDING=""
if [ -f "$SCRIPT_DIR/watch_channels_biz.txt" ]; then
  BIZ_OUT=$("$SCRIPT_DIR/venv/bin/python" "$SCRIPT_DIR/watch.py" --days "$DAYS" $EXTRA \
    --channels "$SCRIPT_DIR/watch_channels_biz.txt" --label биз 2>>"$LOG")
  BIZ_DIGEST=$(echo "$BIZ_OUT" | grep '^DIGEST=' | cut -d= -f2-)
  BIZ_HAS_SIGNALS=$(echo "$BIZ_OUT" | grep '^HAS_SIGNALS=' | cut -d= -f2-)
  BIZ_STATE_PENDING=$(echo "$BIZ_OUT" | grep '^STATE_PENDING=' | cut -d= -f2-)
  if [ -n "$BIZ_DIGEST" ] && [ -f "$BIZ_DIGEST" ]; then
    log "biz-digest: $BIZ_DIGEST"
  else
    log "WARN: биз-сбор не удался — сводка без блока предпринимателей"
    BIZ_DIGEST=""
    BIZ_HAS_SIGNALS="0"
  fi
fi

push_vault() {
  if [ "${TRENDWATCH_GIT_SYNC:-0}" = "1" ]; then
    git -C "$VAULT" add "projects/channel/trendwatching" >> "$LOG" 2>&1
    git -C "$VAULT" commit -m "trendwatch: авто-дайджест $(date +%Y-%m-%d)" >> "$LOG" 2>&1 \
      && git -C "$VAULT" push >> "$LOG" 2>&1 || log "WARN: git push не прошёл"
  fi
}

# --- сигнальный режим в тихий день: молчим, токены не тратим
if [ "$MODE" = "signals" ] && [ "$HAS_SIGNALS" = "0" ] && [ "$BIZ_HAS_SIGNALS" = "0" ]; then
  log "сигналов нет — ничего не шлём"
  push_vault
  exit 0
fi

# --- в полном режиме: ротация старых файлов (сигнальные и сырые EN старше 30 дней —
# мусор; воскресные дайджесты и EN-недели храним как архив насмотренности)
EN_DIGEST=""
if [ "$MODE" = "full" ]; then
  TW_DIR="$VAULT/projects/channel/trendwatching"
  ROTATED=$(find "$TW_DIR" -name "*сигналы*" -mtime +30 -print -delete 2>/dev/null | wc -l | tr -d ' ')
  ROTATED2=$(find "$TW_DIR" -name "*en-дайджест*" -mtime +30 -print -delete 2>/dev/null | wc -l | tr -d ' ')
  log "ротация: удалено файлов сигналов $ROTATED, сырых en-дайджестов $ROTATED2"
  EN_OUT=$("$SCRIPT_DIR/venv/bin/python" "$SCRIPT_DIR/sources_watch.py" --days 7 --per-source 7 --full 2>>"$LOG")
  EN_DIGEST=$(echo "$EN_OUT" | grep '^DIGEST=' | cut -d= -f2-)
  if [ -n "$EN_DIGEST" ] && [ -f "$EN_DIGEST" ]; then
    log "en-digest: $EN_DIGEST"
  else
    log "WARN: EN-дайджест не собрался — сводка без EN-блока"
    EN_DIGEST=""
  fi
fi

# --- разбор headless Claude
if [ "$MODE" = "full" ]; then
  PROMPT_FILE="$SCRIPT_DIR/trendwatch-prompt.md"
else
  PROMPT_FILE="$SCRIPT_DIR/trendwatch-signals-prompt.md"
fi
DIGEST_REL="${DIGEST#"$VAULT"/}"
EN_DIGEST_REL=""
[ -n "$EN_DIGEST" ] && EN_DIGEST_REL="${EN_DIGEST#"$VAULT"/}"
BIZ_DIGEST_REL=""
[ -n "$BIZ_DIGEST" ] && BIZ_DIGEST_REL="${BIZ_DIGEST#"$VAULT"/}"
TODAY=$(date +%Y-%m-%d)
EN_WEEKLY_REL="projects/channel/trendwatching/{channel} {source} трендвотчинг en-неделя – $TODAY.md"
EN_WEEKLY_FILE="$VAULT/$EN_WEEKLY_REL"
PROMPT=$(python3 - "$PROMPT_FILE" <<PYEOF
import sys
tpl = open(sys.argv[1]).read()
en = '''$EN_DIGEST_REL''' or '(EN-дайджест не собрался, пропусти EN-блок)'
biz = '''$BIZ_DIGEST_REL''' or '(биз-дайджест пуст — пропусти блок предпринимателей)'
print(tpl.replace('{{TODAY}}', '$TODAY')
         .replace('{{DIGEST}}', '''$DIGEST_REL''')
         .replace('{{EN_WEEKLY_FILE}}', '''$EN_WEEKLY_REL''')
         .replace('{{EN_DIGEST}}', en)
         .replace('{{BIZ_DIGEST}}', biz))
PYEOF
)

SUMMARY_FILE=$(mktemp)
cd "$VAULT" || { log "ERROR: волт недоступен"; exit 1; }
CLAUDE_START=$SECONDS
"$CLAUDE_BIN" -p "$PROMPT" \
  --model "$CLAUDE_MODEL" \
  --permission-mode acceptEdits \
  --output-format text \
  > "$SUMMARY_FILE" 2>>"$LOG"
RC=$?
CLAUDE_SEC=$((SECONDS - CLAUDE_START))

SUMMARY=$(cat "$SUMMARY_FILE"); rm -f "$SUMMARY_FILE"

# Разбор сбоя возможен только по тому, что мы сохранили. 01.08.2026 не осталось
# ничего: stdout уходил в mktemp-файл и удалялся, stderr был пуст, а в логе
# стояла захардкоженная «сводка пустая» — она не отличала молчаливую смерть от
# ругани в stdout. Длительность здесь такая же улика: две секунды вместо минуты
# означают мгновенный отказ, а не таймаут и не долгий разбор.
if [ $RC -ne 0 ] || [ -z "$SUMMARY" ]; then
  log "ERROR: claude rc=$RC за $CLAUDE_SEC с, stdout ${#SUMMARY} знаков — ничего не шлём"
  if [ -n "$SUMMARY" ]; then
    { echo "--- stdout claude (до 2000 знаков) ---"
      printf '%.2000s\n' "$SUMMARY"
      echo "--- конец stdout ---"; } >> "$LOG"
  else
    log "stdout пуст; если и stderr выше пуст — claude умер молча"
  fi
  push_vault
  exit 1
fi

# --- полный режим: дайджесты публикуем на Telegraph (кликабельные ссылки,
# Instant View в ТГ — md-файл на телефоне читается плоским текстом). Ссылки
# добавляем к сводке. Файл в волте остаётся markdown. Fallback — sendDocument.
TG="https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN"

publish_tg() {  # $1 md-файл, $2 подпись → печатает строку-ссылку или шлёт файл
  local file="$1" caption="$2" url
  url=$("$SCRIPT_DIR/venv/bin/python" "$SCRIPT_DIR/telegraph.py" "$file" 2>>"$LOG")
  if [ -n "$url" ]; then
    log "telegraph[$caption]: $url"
    printf '%s: %s' "$caption" "$url"
  else
    log "WARN: telegraph[$caption] не удался — шлю файлом"
    local copy="$LOG_DIR/$(basename "$file")"
    printf '\xEF\xBB\xBF' > "$copy"; cat "$file" >> "$copy"
    curl -s -o /dev/null -X POST "$TG/sendDocument" \
      -F "chat_id=$TELEGRAM_OWNER_USER_ID" -F "document=@$copy" \
      -F "caption=$caption $TODAY"
    rm -f "$copy"
  fi
}

LINKS=""
if [ "$MODE" = "full" ]; then
  L=$(publish_tg "$DIGEST" "📊 Дайджест трендов")
  [ -n "$L" ] && LINKS="$LINKS"$'\n'"$L"
  EN_SEND="$EN_WEEKLY_FILE"
  if [ ! -f "$EN_SEND" ]; then
    log "WARN: Claude не записал EN-неделю — беру сырой список"
    EN_SEND="$EN_DIGEST"
  fi
  if [ -n "$EN_SEND" ] && [ -f "$EN_SEND" ]; then
    L=$(publish_tg "$EN_SEND" "🌍 EN-неделя")
    [ -n "$L" ] && LINKS="$LINKS"$'\n'"$L"
  fi
  if [ -n "$BIZ_DIGEST" ] && [ -f "$BIZ_DIGEST" ]; then
    L=$(publish_tg "$BIZ_DIGEST" "🚀 Тренды предпринимателей")
    [ -n "$L" ] && LINKS="$LINKS"$'\n'"$L"
  fi
fi

# --- сводка + ссылки одним сообщением
MSG="$SUMMARY"
[ -n "$LINKS" ] && MSG="$SUMMARY"$'\n'"$LINKS"
SEND_RC=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$TG/sendMessage" \
  --data-urlencode "chat_id=$TELEGRAM_OWNER_USER_ID" \
  --data-urlencode "text=$MSG")
log "sendMessage: HTTP $SEND_RC"

# --- дедуп двигаем только теперь: сигналы правда доехали до Наташи.
# Раньше watch.py помечал посты показанными на сборе, и любой сбой ниже по
# течению (01.08.2026 — молчаливый rc=1 у Claude) съедал день сигналов: они
# уже «показаны», а сообщение не ушло. Не промоутим — повторятся завтра.
promote_state() {  # $1 pending-файл → двигаем в боевое состояние
  local p="$1"
  [ -n "$p" ] && [ -f "$p" ] || return 0
  mv "$p" "${p%.pending}" && log "дедуп сдвинут: $(basename "${p%.pending}")"
}
if [ "$SEND_RC" = "200" ]; then
  promote_state "$STATE_PENDING"
  promote_state "$BIZ_STATE_PENDING"
else
  log "WARN: сообщение не ушло (HTTP $SEND_RC) — дедуп не двигаю, сигналы повторятся завтра"
fi

push_vault
log "done"
