#!/usr/bin/env bash
# API smoke tests — chạy trên server đang lắng nghe.
set -euo pipefail

# shellcheck source=../lib/common.sh
source "$(dirname "$0")/../lib/common.sh"

BASE="${1:-http://127.0.0.1:${LM_TEST_PORT}}"
MISSION_ID="${TEST_MISSION_ID:-}"

RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
PASS=0
FAIL=0

log_pass() { echo -e "${GREEN}PASS${NC} $*"; PASS=$((PASS + 1)); }
log_fail() { echo -e "${RED}FAIL${NC} $*"; FAIL=$((FAIL + 1)); }

json_field() {
  python3 - "$1" "$2" <<'PY'
import json, sys
doc = json.loads(open(sys.argv[1]).read())
path = sys.argv[2].split(".")
cur = doc
for p in path:
    cur = cur[p]
print(cur)
PY
}

http_code() {
  curl -s -o "$1" -w '%{http_code}' "${CURL_OPTS[@]}" "${@:2}"
}

assert_code() {
  local name="$1" expect="$2" file="$3"
  shift 3
  local code
  code="$(http_code "$file" "$@")"
  if [[ "$code" == "$expect" ]]; then
    log_pass "$name (HTTP $code)"
  else
    log_fail "$name — expected HTTP $expect, got $code"
    [[ -f "$file" ]] && head -c 400 "$file" >&2 || true
    echo >&2
  fi
}

assert_json_true() {
  local name="$1" file="$2" expr="$3"
  if python3 - "$file" "$expr" <<'PY'
import json, sys
doc = json.loads(open(sys.argv[1]).read())
env = {"doc": doc, "any": any, "all": all, "len": len, "list": list, "isinstance": isinstance}
ok = eval(sys.argv[2], {"__builtins__": {}}, env)
sys.exit(0 if ok else 1)
PY
  then
    log_pass "$name"
  else
    log_fail "$name"
    head -c 400 "$file" >&2 || true
    echo >&2
  fi
}

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
COOKIE_JAR="$TMP/cookies.txt"
CURL_OPTS=()

echo "API smoke tests → $BASE"
echo

# --- Health & static (no auth) ---
assert_code "GET /api/health" 200 "$TMP/health.json" -X GET "$BASE/api/health"
assert_json_true "health ok" "$TMP/health.json" 'doc.get("ok") is True'

assert_code "GET /api/state without auth" 401 "$TMP/state_unauth.json" -X GET "$BASE/api/state"

assert_code "POST /api/auth/login bad password" 401 "$TMP/login_bad.json" \
  -X POST "$BASE/api/auth/login" \
  -H 'Content-Type: application/json' \
  -d '{"username":"Admin","password":"wrong"}'

assert_code "POST /api/auth/login" 200 "$TMP/login.json" \
  -X POST "$BASE/api/auth/login" \
  -H 'Content-Type: application/json' \
  -c "$COOKIE_JAR" \
  -d '{"username":"Admin","password":"admin"}'
assert_json_true "login user" "$TMP/login.json" 'doc.get("user",{}).get("username") == "Admin"'

CURL_OPTS=(-b "$COOKIE_JAR" -c "$COOKIE_JAR")

assert_code "GET /api/auth/me" 200 "$TMP/me.json" -X GET "$BASE/api/auth/me"
assert_json_true "auth me" "$TMP/me.json" 'doc.get("user",{}).get("group_name") == "Administrators"'

assert_code "GET /" 200 "$TMP/index.html" -X GET "$BASE/"
assert_code "GET /auth.js" 200 "$TMP/auth.js" -X GET "$BASE/auth.js"
assert_code "GET /missions.js" 200 "$TMP/missions.js" -X GET "$BASE/missions.js"
assert_code "GET /topbar.js" 200 "$TMP/topbar.js" -X GET "$BASE/topbar.js"

assert_code "GET /api/robot/status" 200 "$TMP/robot_status.json" -X GET "$BASE/api/robot/status"
assert_json_true "robot status motion" "$TMP/robot_status.json" 'doc.get("motion") in ("paused", "running")'
assert_json_true "robot status battery" "$TMP/robot_status.json" 'doc.get("battery_percent", 0) >= 0'

assert_code "POST /api/robot/start" 200 "$TMP/robot_start.json" \
  -X POST "$BASE/api/robot/start" -H 'Content-Type: application/json' -d '{}'
assert_json_true "robot started" "$TMP/robot_start.json" 'doc.get("motion") == "running"'

assert_code "POST /api/robot/pause" 200 "$TMP/robot_pause.json" \
  -X POST "$BASE/api/robot/pause" -H 'Content-Type: application/json' -d '{}'
assert_json_true "robot paused" "$TMP/robot_pause.json" 'doc.get("motion") == "paused"'

assert_code "POST /api/robot/errors/reset" 200 "$TMP/robot_reset.json" \
  -X POST "$BASE/api/robot/errors/reset" -H 'Content-Type: application/json' -d '{}'
assert_json_true "robot health ok" "$TMP/robot_reset.json" 'doc.get("health") == "ok"'

assert_code "PUT /api/auth/profile" 200 "$TMP/profile.json" \
  -X PUT "$BASE/api/auth/profile" \
  -H 'Content-Type: application/json' \
  -d '{"display_name":"Admin Test"}'
assert_json_true "profile display_name" "$TMP/profile.json" 'doc.get("user",{}).get("display_name") == "Admin Test"'

assert_code "GET /api/state" 200 "$TMP/state.json" -X GET "$BASE/api/state"

assert_code "GET /api/missions" 200 "$TMP/missions.json" -X GET "$BASE/api/missions"

if [[ -z "$MISSION_ID" ]]; then
  MISSION_ID="$(python3 -c "
import json
doc = json.load(open('$TMP/missions.json'))
missions = doc.get('missions', [])
preferred = 'testmission00001'
ids = [m.get('id') for m in missions if isinstance(m, dict)]
if preferred in ids:
    print(preferred)
elif ids:
    print(ids[0])
")"
fi

if [[ -z "$MISSION_ID" ]]; then
  log_fail "no mission available for queue tests"
  exit 1
fi

echo "Mission id: $MISSION_ID"
echo

assert_json_true "missions available" "$TMP/missions.json" 'len(doc.get("missions", [])) >= 1'

# --- Queue pause/continue (chạy sớm, trước các test enqueue khác) ---
curl -s "${CURL_OPTS[@]}" -X DELETE "$BASE/api/mission_queue" -o /dev/null || true
curl -s "${CURL_OPTS[@]}" -X POST "$BASE/api/mission_queue" \
  -H 'Content-Type: application/json' \
  -d "{\"mission_id\":\"$MISSION_ID\"}" -o "$TMP/qpost.json"
for _ in $(seq 1 30); do
  curl -s "${CURL_OPTS[@]}" "$BASE/api/mission_queue" -o "$TMP/runner_poll.json"
  RUNNER_STATE="$(python3 -c "import json; print(json.load(open('$TMP/runner_poll.json')).get('runner',{}).get('state',''))")"
  if [[ "$RUNNER_STATE" == "running" ]]; then
    break
  fi
  sleep 0.2
done
if [[ "$RUNNER_STATE" == "running" ]]; then
  assert_code "POST /api/mission_queue/pause" 200 "$TMP/pause.json" \
    -X POST "$BASE/api/mission_queue/pause"
  assert_json_true "runner paused" "$TMP/pause.json" 'doc.get("state") == "paused"'
  assert_code "POST /api/mission_queue/continue" 200 "$TMP/cont.json" \
    -X POST "$BASE/api/mission_queue/continue"
  assert_json_true "runner not paused" "$TMP/cont.json" 'doc.get("state") != "paused"'
  for _ in $(seq 1 15); do
    curl -s "${CURL_OPTS[@]}" "$BASE/api/mission_queue" -o "$TMP/runner_poll.json"
    RUNNER_STATE="$(python3 -c "import json; print(json.load(open('$TMP/runner_poll.json')).get('runner',{}).get('state',''))")"
    if [[ "$RUNNER_STATE" == "running" || "$RUNNER_STATE" == "paused" ]]; then
      break
    fi
    sleep 0.2
  done
  if [[ "$RUNNER_STATE" == "running" || "$RUNNER_STATE" == "paused" ]]; then
    assert_code "POST /api/mission_queue/cancel" 200 "$TMP/cancel.json" \
      -X POST "$BASE/api/mission_queue/cancel"
    for _ in $(seq 1 40); do
      curl -s "${CURL_OPTS[@]}" "$BASE/api/mission_queue" -o "$TMP/runner_poll.json"
      RUNNER_STATE="$(python3 -c "import json; print(json.load(open('$TMP/runner_poll.json')).get('runner',{}).get('state',''))")"
      if [[ "$RUNNER_STATE" == "idle" ]]; then
        break
      fi
      sleep 0.15
    done
    assert_json_true "runner idle after cancel" "$TMP/runner_poll.json" \
      'doc.get("runner",{}).get("state") == "idle"'
  else
    log_fail "runner not active for cancel test (skipped)"
  fi
else
  log_fail "runner never reached running (pause test skipped)"
fi

assert_code "POST /api/mission_queue/cancel idle" 400 "$TMP/cancel_idle.json" \
  -X POST "$BASE/api/mission_queue/cancel"

# --- LiDAR CRUD ---
assert_code "POST /api/lidars" 201 "$TMP/lidar.json" \
  -X POST "$BASE/api/lidars" \
  -H 'Content-Type: application/json' \
  -d '{"name":"smoke-lidar","ip":"192.168.99.1","port":2112}'
LIDAR_ID="$(json_field "$TMP/lidar.json" id 2>/dev/null || echo "")"
if [[ -n "$LIDAR_ID" ]]; then
  log_pass "lidar created id=$LIDAR_ID"
  PASS=$((PASS + 1))
  assert_code "PUT /api/lidars" 200 "$TMP/lidar_put.json" \
    -X PUT "$BASE/api/lidars/$LIDAR_ID" \
    -H 'Content-Type: application/json' \
    -d '{"name":"smoke-lidar","ip":"192.168.99.2","port":2112}'
  assert_code "DELETE /api/lidars" 204 "$TMP/lidar_del.txt" \
    -X DELETE "$BASE/api/lidars/$LIDAR_ID"
else
  log_fail "lidar create — no id in response"
fi

# --- IMU CRUD ---
assert_code "POST /api/imus" 201 "$TMP/imu.json" \
  -X POST "$BASE/api/imus" \
  -H 'Content-Type: application/json' \
  -d '{"name":"smoke-imu","frame_id":"imu_smoke","topic":"/imu/smoke","source":"external"}'
IMU_ID="$(json_field "$TMP/imu.json" id 2>/dev/null || echo "")"
if [[ -n "$IMU_ID" ]]; then
  log_pass "imu created id=$IMU_ID"
  PASS=$((PASS + 1))
  assert_code "DELETE /api/imus" 204 "$TMP/imu_del.txt" -X DELETE "$BASE/api/imus/$IMU_ID"
else
  log_fail "imu create — no id in response"
fi

# --- Clear queue ---
curl -s "${CURL_OPTS[@]}" -X DELETE "$BASE/api/mission_queue" -o /dev/null || true
curl -s "${CURL_OPTS[@]}" -X DELETE "$BASE/api/v2.0.0/mission_queue" -o /dev/null || true

# --- MiR v2 enqueue (Cách C REST) ---
assert_code "POST /api/v2.0.0/mission_queue" 201 "$TMP/v2q.json" \
  -X POST "$BASE/api/v2.0.0/mission_queue" \
  -H 'Content-Type: application/json' \
  -d "{\"mission_id\":\"$MISSION_ID\",\"priority\":2,\"robot_id\":\"default\"}"
assert_json_true "v2 queue entry mission_id" "$TMP/v2q.json" \
  'doc.get("mission_id") == "'"$MISSION_ID"'"'

assert_code "GET /api/v2.0.0/mission_queue" 200 "$TMP/v2list.json" -X GET "$BASE/api/v2.0.0/mission_queue"
for _ in $(seq 1 15); do
  curl -s "${CURL_OPTS[@]}" "$BASE/api/v2.0.0/mission_queue" -o "$TMP/v2list.json"
  if python3 -c "import json; d=json.load(open('$TMP/v2list.json')); exit(0 if isinstance(d,list) and len(d)>=1 else 1)" 2>/dev/null; then
    break
  fi
  sleep 0.2
done
assert_json_true "v2 queue non-empty" "$TMP/v2list.json" 'isinstance(doc, list) and len(doc) >= 1'

assert_code "GET /api/v2.0.0/status" 200 "$TMP/v2status.json" -X GET "$BASE/api/v2.0.0/status"
assert_json_true "v2 status has state_text" "$TMP/v2status.json" '"state_text" in doc'

# --- Modbus trigger (Cách C) ---
assert_code "POST /api/triggers" 201 "$TMP/trig.json" \
  -X POST "$BASE/api/triggers" \
  -H 'Content-Type: application/json' \
  -d "{\"name\":\"smoke-trigger\",\"coil_id\":1001,\"mission_id\":\"$MISSION_ID\"}"
TRIG_ID="$(json_field "$TMP/trig.json" id 2>/dev/null || echo "")"
if [[ -n "$TRIG_ID" ]]; then
  log_pass "trigger created id=$TRIG_ID"
  PASS=$((PASS + 1))
  assert_code "POST modbus fire coil" 200 "$TMP/fire.json" \
    -X POST "$BASE/api/modbus/coils/1001/trigger"
  assert_code "DELETE /api/triggers" 204 "$TMP/trig_del.txt" \
    -X DELETE "$BASE/api/triggers/$TRIG_ID"
else
  log_fail "trigger create — no id"
fi

# --- Fleet schedule ---
assert_code "POST /api/fleet/schedules" 201 "$TMP/sched.json" \
  -X POST "$BASE/api/fleet/schedules" \
  -H 'Content-Type: application/json' \
  -d "{\"name\":\"smoke-schedule\",\"mission_id\":\"$MISSION_ID\",\"start_mode\":\"asap\",\"priority\":1}"
SCHED_ID="$(json_field "$TMP/sched.json" id 2>/dev/null || echo "")"
if [[ -n "$SCHED_ID" ]]; then
  log_pass "schedule created id=$SCHED_ID"
  PASS=$((PASS + 1))
  assert_code "DELETE /api/fleet/schedules" 204 "$TMP/sched_del.txt" \
    -X DELETE "$BASE/api/fleet/schedules/$SCHED_ID"
else
  log_fail "schedule create — no id"
fi

assert_code "GET /api/fleet/robots" 200 "$TMP/robots.json" -X GET "$BASE/api/fleet/robots"
assert_json_true "robots list" "$TMP/robots.json" 'isinstance(doc, list) and len(doc) >= 1'

echo
echo "Results: $PASS passed, $FAIL failed"
if [[ "$FAIL" -gt 0 ]]; then
  exit 1
fi
