#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BACKEND_DIR="$ROOT_DIR/backend"

TMP_HOME="${TMP_HOME:-/tmp/opencode-clean}"
TMP_DATA_DIR="${TMP_DATA_DIR:-/tmp/investibook-local-data}"
BACKEND_PORT="${BACKEND_PORT:-8000}"
BACKEND_LOG="${BACKEND_LOG:-/tmp/investibook-local-backend.log}"
CHAT_TIMEOUT_S="${CHAT_TIMEOUT_S:-300}"
SOURCE_PREPARE_FIXTURES_DIR="${SOURCE_PREPARE_FIXTURES_DIR:-$ROOT_DIR/backend/tests/fixtures/source_prepare}"
RUN_CASE1="${RUN_CASE1:-1}"
RUN_CASE2="${RUN_CASE2:-1}"

CASE1_NAME="${CASE1_NAME:-Local Case 1 TiKV hotspot}"
CASE2_NAME="${CASE2_NAME:-Local Case 2 O11Y-2447801}"
CASE2_ISSUE_ID="${CASE2_ISSUE_ID:-O11Y-2447801}"

CASE1_PROMPT="${CASE1_PROMPT:-查一下 10754652599988043412 集群 2026-04-05 11:30 到 12:30 前后 TiKV 热点问题。请直接给出最相关的数据和结论。}"
CASE2_PROMPT="${CASE2_PROMPT:-O11Y-2447801 查下 QPS 整体变化曲线，给出最相关的数据和结论。必须实际执行查询并给出结果，不要只输出计划。不要跑 collect_evidence 或完整 diagnosis run；直接用 clinic-cli metrics 子命令查询。}"
CASE2_FOLLOWUP_PROMPT="${CASE2_FOLLOWUP_PROMPT:-继续看每个 TiDB instance 的 QPS 曲线，给出各实例的 avg/max 和是否有明显倾斜。必须实际执行一次 clinic-cli metrics 子命令，PromQL 使用 sum(rate(tidb_server_query_total[1m])) by (instance)。直接使用命令输出里的 summary/series_summaries 诊断；不要加 --inline-chart-data，不要 import helper，不要自己写 Python 解析。}"

TOKEN=""
WS1=""
WS2=""
BACKEND_PID=""

if [[ "${RUN_CASE1}" != "1" && "${RUN_CASE2}" != "1" ]]; then
  echo "At least one of RUN_CASE1=1 or RUN_CASE2=1 is required" >&2
  exit 1
fi

log() {
  printf '\n[%s] %s\n' "$(date '+%H:%M:%S')" "$*"
}

cleanup() {
  if [[ -n "${BACKEND_PID}" ]] && kill -0 "${BACKEND_PID}" 2>/dev/null; then
    log "Stopping backend pid=${BACKEND_PID}"
    kill "${BACKEND_PID}" 2>/dev/null || true
    wait "${BACKEND_PID}" 2>/dev/null || true
  fi
}

trap cleanup EXIT

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || {
    echo "missing command: $1" >&2
    exit 1
  }
}

require_cmd curl
require_cmd python3
require_cmd uv
require_cmd lsof

if lsof -iTCP:"${BACKEND_PORT}" -sTCP:LISTEN -n -P >/dev/null 2>&1; then
  echo "backend port ${BACKEND_PORT} is already in use; stop the existing local backend first" >&2
  exit 1
fi

mkdir -p "${TMP_HOME}" "${TMP_DATA_DIR}"

wait_http() {
  local url="$1"
  for _ in $(seq 1 120); do
    if curl -fsS "${url}" >/dev/null 2>&1; then
      return 0
    fi
    sleep 1
  done
  echo "timed out waiting for ${url}" >&2
  return 1
}

create_dev_token() {
  (
    cd "${BACKEND_DIR}"
    HOME="${TMP_HOME}" DATA_DIR="${TMP_DATA_DIR}" uv run python - <<'PY'
from investibook.db.queries import _db_init, _db_get_or_create_user, _db_create_user_session, _hash_password, _db_connect, _db_lock
_db_init()
user = _db_get_or_create_user('dev@localhost')
pw_hash = _hash_password('dev12345')
with _db_lock, _db_connect() as conn:
    conn.execute('UPDATE users SET password_hash = ? WHERE id = ?', (pw_hash, user['id']))
    conn.commit()
token = _db_create_user_session(user['id'])
print(token)
PY
  )
}

create_workspace() {
  local name="$1"
  curl -fsS -X POST "http://localhost:${BACKEND_PORT}/api/workspaces" \
    -H "Authorization: Bearer ${TOKEN}" \
    -H 'Content-Type: application/json' \
    -d "{\"name\": $(python3 - <<'PY' "$name"
import json, sys
print(json.dumps(sys.argv[1]))
PY
)}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["id"])'
}

prepare_case2_source() {
  log "Adding Jira source for ${CASE2_ISSUE_ID}"
  curl -fsS -X POST "http://localhost:${BACKEND_PORT}/api/workspace/${WS2}/sources" \
    -H "Authorization: Bearer ${TOKEN}" \
    -H 'Content-Type: application/json' \
    -d "{\"type\":\"jira_issue\",\"name\":\"${CASE2_ISSUE_ID}\",\"issueId\":\"${CASE2_ISSUE_ID}\",\"status\":\"loading\"}" \
    >/tmp/${WS2}-source.json

  local task
  task="$(curl -fsS -X POST "http://localhost:${BACKEND_PORT}/api/sources/prepare" \
    -H "Authorization: Bearer ${TOKEN}" \
    -H 'Content-Type: application/json' \
    -d "{\"workspace_id\":\"${WS2}\",\"issue_id\":\"${CASE2_ISSUE_ID}\"}" \
    | python3 -c 'import sys,json; print(json.load(sys.stdin)["task_id"])')"

  log "Prepare task=${task}"
  for _ in $(seq 1 40); do
    local status
    status="$(curl -fsS "http://localhost:${BACKEND_PORT}/api/sources/prepare/${task}" \
      -H "Authorization: Bearer ${TOKEN}" \
      | python3 -c 'import sys,json; print(json.load(sys.stdin)["status"])')"
    echo "prepare status=${status}"
    if [[ "${status}" == "completed" ]]; then
      curl -fsS "http://localhost:${BACKEND_PORT}/api/sources/prepare/${task}" \
        -H "Authorization: Bearer ${TOKEN}" >"/tmp/${WS2}-prepare.json"
      return 0
    fi
    if [[ "${status}" == "error" ]]; then
      curl -fsS "http://localhost:${BACKEND_PORT}/api/sources/prepare/${task}" \
        -H "Authorization: Bearer ${TOKEN}" >"/tmp/${WS2}-prepare.json"
      return 1
    fi
    sleep 2
  done
  return 1
}

run_case_chat() {
  local ws_id="$1"
  local prompt="$2"
  local label="${3:-chat}"
  local output="/tmp/${ws_id}-${label}.sse"

  log "Running chat for ${ws_id} label=${label}"
  local user_message_id="user-$(date +%s%3N)"
  timeout "${CHAT_TIMEOUT_S}s" curl -sS -N -G "http://localhost:${BACKEND_PORT}/api/chat/stream" \
    --data-urlencode "message=${prompt}" \
    --data-urlencode "workspace_id=${ws_id}" \
    --data-urlencode "user_message_id=${user_message_id}" \
    --data-urlencode "token=${TOKEN}" \
    >"${output}" || true

  log "Saved SSE to ${output}"
  python3 - <<'PY' "${output}"
from pathlib import Path
import sys
p = Path(sys.argv[1])
text = p.read_text(errors="ignore") if p.exists() else ""
print(f"sse_size={len(text)}")
print(text[:3000])
print("---tail---")
print(text[-1200:])
PY
}

workspace_evidence_state() {
  local ws_id="$1"
  local report="${TMP_DATA_DIR}/workspaces/${ws_id}/evidences/workspace_evidence_report.json"
  python3 - <<'PY' "${report}"
from pathlib import Path
import json
import sys
p = Path(sys.argv[1])
if not p.exists():
    print(json.dumps({"exists": False}))
    raise SystemExit(0)
data = json.loads(p.read_text(encoding="utf-8"))
meta = data.get("metadata") or {}
evidence = data.get("evidence") or {}
analysis = data.get("analysis") or {}
state = {
    "exists": True,
    "last_raw_message_id": (meta.get("last_raw_evidence") or {}).get("message_id"),
    "last_raw_kind": (meta.get("last_raw_evidence") or {}).get("kind"),
    "last_analysis_message_id": (meta.get("last_analysis_update") or {}).get("message_id"),
    "metric_count": len(evidence.get("metrics") or []),
    "query_count": len(evidence.get("queries") or []),
    "log_count": len(evidence.get("logs") or []),
    "analysis_ready": bool(analysis),
}
print(json.dumps(state, ensure_ascii=False))
PY
}

inspect_workspace() {
  local ws_id="$1"
  local ws_dir="${TMP_DATA_DIR}/workspaces/${ws_id}"
  log "Inspecting ${ws_id}"
  echo "workspace_dir=${ws_dir}"
  echo "evidences:"
  find "${ws_dir}/evidences" -maxdepth 2 -type f 2>/dev/null | sort || true
}

wait_for_workspace_analysis() {
  local ws_id="$1"
  local report="${TMP_DATA_DIR}/workspaces/${ws_id}/evidences/workspace_evidence_report.json"
  log "Waiting for workspace evidence analysis for ${ws_id}"
  for _ in $(seq 1 60); do
    if python3 - <<'PY' "${report}"
from pathlib import Path
import json
import sys
p = Path(sys.argv[1])
if not p.exists():
    raise SystemExit(1)
try:
    data = json.loads(p.read_text(encoding="utf-8"))
except Exception:
    raise SystemExit(1)
analysis = data.get("analysis")
meta = data.get("metadata") or {}
if isinstance(analysis, dict) and analysis:
    print("analysis_ready")
    raise SystemExit(0)
if meta.get("last_analysis_update"):
    print("analysis_updated_at")
    raise SystemExit(0)
raise SystemExit(1)
PY
    then
      return 0
    fi
    sleep 1
  done
  echo "analysis not ready after wait window" >&2
  return 1
}

wait_for_workspace_raw_change() {
  local ws_id="$1"
  local previous_message_id="$2"
  local previous_metric_count="$3"
  log "Waiting for new raw evidence for ${ws_id} previous_message_id=${previous_message_id} previous_metric_count=${previous_metric_count}"
  for _ in $(seq 1 90); do
    local state
    state="$(workspace_evidence_state "${ws_id}")"
    echo "evidence_state=${state}"
    if python3 - <<'PY' "${state}" "${previous_message_id}" "${previous_metric_count}"
import json
import sys
state = json.loads(sys.argv[1])
previous_message_id = sys.argv[2]
try:
    previous_metric_count = int(sys.argv[3] or 0)
except ValueError:
    previous_metric_count = 0
message_id = state.get("last_raw_message_id") or ""
metric_count = int(state.get("metric_count") or 0)
if metric_count > previous_metric_count and (not previous_message_id or message_id != previous_message_id):
    raise SystemExit(0)
raise SystemExit(1)
PY
    then
      return 0
    fi
    sleep 1
  done
  echo "new raw evidence not observed after wait window" >&2
  return 1
}

wait_for_workspace_analysis_change() {
  local ws_id="$1"
  local expected_message_id="$2"
  log "Waiting for authored analysis update for ${ws_id} expected_message_id=${expected_message_id}"
  for _ in $(seq 1 90); do
    local state
    state="$(workspace_evidence_state "${ws_id}")"
    echo "analysis_state=${state}"
    if python3 - <<'PY' "${state}" "${expected_message_id}"
import json
import sys
state = json.loads(sys.argv[1])
expected_message_id = sys.argv[2]
if state.get("last_analysis_message_id") == expected_message_id and state.get("analysis_ready"):
    raise SystemExit(0)
raise SystemExit(1)
PY
    then
      return 0
    fi
    sleep 1
  done
  echo "authored analysis update not observed after wait window" >&2
  return 1
}

wait_for_workspace_analysis_after() {
  local ws_id="$1"
  local previous_message_id="$2"
  log "Waiting for authored analysis update for ${ws_id} previous_message_id=${previous_message_id}"
  for _ in $(seq 1 90); do
    local state
    state="$(workspace_evidence_state "${ws_id}")"
    echo "analysis_state=${state}"
    if python3 - <<'PY' "${state}" "${previous_message_id}"
import json
import sys
state = json.loads(sys.argv[1])
previous_message_id = sys.argv[2]
message_id = state.get("last_analysis_message_id") or ""
if message_id and message_id != previous_message_id and state.get("analysis_ready"):
    raise SystemExit(0)
raise SystemExit(1)
PY
    then
      return 0
    fi
    sleep 1
  done
  echo "authored analysis update not observed after wait window" >&2
  return 1
}

log "Starting clean local backend"
(
  cd "${BACKEND_DIR}"
  HOME="${TMP_HOME}" \
  DATA_DIR="${TMP_DATA_DIR}" \
  INVESTIBOOK_SOURCE_PREPARE_FIXTURES_DIR="${SOURCE_PREPARE_FIXTURES_DIR}" \
  ACP_PREWARM_ON_WORKSPACE_CREATE=false \
  bash -lc 'set -a; [ -f .env.local ] && . ./.env.local; set +a; ACP_AGENT_TYPE=opencode uv run uvicorn investibook.main:app --reload --reload-dir investibook --reload-dir tests --reload-dir scripts --reload-exclude ".venv" --host 0.0.0.0 --port "'"${BACKEND_PORT}"'" --timeout-graceful-shutdown 3'
) >"${BACKEND_LOG}" 2>&1 &
BACKEND_PID="$!"

wait_http "http://localhost:${BACKEND_PORT}/api/config"
log "Backend ready on :${BACKEND_PORT}"

TOKEN="$(create_dev_token)"
log "Created dev token"

if [[ "${RUN_CASE1}" == "1" ]]; then
  WS1="$(create_workspace "${CASE1_NAME}")"
  log "WS1=${WS1}"
fi

if [[ "${RUN_CASE2}" == "1" ]]; then
  WS2="$(create_workspace "${CASE2_NAME}")"
  log "WS2=${WS2}"
  prepare_case2_source || true
fi

if [[ "${RUN_CASE1}" == "1" ]]; then
  run_case_chat "${WS1}" "${CASE1_PROMPT}" "case1"
fi

if [[ "${RUN_CASE2}" == "1" ]]; then
  run_case_chat "${WS2}" "${CASE2_PROMPT}" "case2-first"
  wait_for_workspace_analysis "${WS2}"
  case2_first_state="$(workspace_evidence_state "${WS2}")"
  log "Case2 first evidence state=${case2_first_state}"
  case2_first_message_id="$(printf '%s' "${case2_first_state}" | python3 -c 'import sys,json; print((json.load(sys.stdin).get("last_raw_message_id") or ""))')"
  case2_first_analysis_message_id="$(printf '%s' "${case2_first_state}" | python3 -c 'import sys,json; print((json.load(sys.stdin).get("last_analysis_message_id") or ""))')"
  case2_first_metric_count="$(printf '%s' "${case2_first_state}" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("metric_count") or 0)')"
  if [[ "${case2_first_metric_count}" -lt 1 ]]; then
    echo "Case2 first turn did not produce raw metric evidence: ${case2_first_state}" >&2
    exit 1
  fi

  run_case_chat "${WS2}" "${CASE2_FOLLOWUP_PROMPT}" "case2-followup"
  wait_for_workspace_raw_change "${WS2}" "${case2_first_message_id}" "${case2_first_metric_count}"
  case2_second_message_id="$(workspace_evidence_state "${WS2}" | python3 -c 'import sys,json; print((json.load(sys.stdin).get("last_raw_message_id") or ""))')"
  if [[ -n "${case2_second_message_id}" ]]; then
    wait_for_workspace_analysis_change "${WS2}" "${case2_second_message_id}"
  else
    wait_for_workspace_analysis_after "${WS2}" "${case2_first_analysis_message_id}"
  fi
  log "Case2 follow-up evidence state=$(workspace_evidence_state "${WS2}")"
fi

if [[ "${RUN_CASE1}" == "1" ]]; then
  inspect_workspace "${WS1}"
fi

if [[ "${RUN_CASE2}" == "1" ]]; then
  inspect_workspace "${WS2}"
fi

log "Backend log tail"
tail -n 200 "${BACKEND_LOG}" || true
