#!/usr/bin/env bash
# LifeOS API smoke test — curl-based.
# Verifies auth + CRUD for tasks, goals, habits, and import/local.
#
# Usage:
#   BASE_URL=https://api.example.com/api ./smoke.sh
#   BASE_URL=https://example.com/api EMAIL=test@x.com PASSWORD=Secret123! ./smoke.sh
#
# Exit code 0 = all green. Non-zero = first failing assertion.

set -u
BASE_URL="${BASE_URL:-http://localhost:8000/api}"
EMAIL="${EMAIL:-smoke+$(date +%s)@lifeos.test}"
PASSWORD="${PASSWORD:-Smoke!Pass123}"
FULL_NAME="${FULL_NAME:-Smoke Tester}"

PASS=0; FAIL=0
say()  { printf "\033[36m▶ %s\033[0m\n" "$*"; }
ok()   { printf "  \033[32m✓\033[0m %s\n" "$*"; PASS=$((PASS+1)); }
bad()  { printf "  \033[31m✗\033[0m %s\n" "$*"; FAIL=$((FAIL+1)); }
die()  { printf "\n\033[31mFAIL\033[0m: %s\n" "$*" >&2; exit 1; }

req() { # METHOD PATH [BODY] -> writes status to $STATUS, body to $BODY
  local m="$1" p="$2" b="${3:-}"
  local hdr=(-H "Accept: application/json" -H "Content-Type: application/json")
  [[ -n "${TOKEN:-}" ]] && hdr+=(-H "Authorization: Bearer $TOKEN")
  local tmp; tmp="$(mktemp)"
  if [[ -n "$b" ]]; then
    STATUS=$(curl -sS -o "$tmp" -w "%{http_code}" -X "$m" "${hdr[@]}" -d "$b" "$BASE_URL$p" || echo "000")
  else
    STATUS=$(curl -sS -o "$tmp" -w "%{http_code}" -X "$m" "${hdr[@]}" "$BASE_URL$p" || echo "000")
  fi
  BODY="$(cat "$tmp")"; rm -f "$tmp"
}

assert_status() { # expected got label
  if [[ "$2" == "$1" ]]; then ok "$3 ($2)"; else bad "$3 — expected $1 got $2 :: $BODY"; fi
}
json_get() { # key -> stdout (uses python for portability)
  python3 -c "import sys,json
try:
  d=json.loads(sys.stdin.read())
  k='$1'.split('.')
  for x in k: d=d[x] if isinstance(d,dict) else d[int(x)]
  print(d)
except Exception as e: print('',end='')"
}

command -v curl >/dev/null || die "curl not installed"
command -v python3 >/dev/null || die "python3 not installed"

say "Base: $BASE_URL"
say "User: $EMAIL"

# ── 1. Health ────────────────────────────────────────────────
say "Health"
req GET /health
assert_status 200 "$STATUS" "GET /health"

# ── 2. Auth: signup → login → me ─────────────────────────────
say "Auth"
req POST /auth/signup "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\",\"full_name\":\"$FULL_NAME\"}"
[[ "$STATUS" == "200" || "$STATUS" == "201" || "$STATUS" == "409" ]] \
  && ok "POST /auth/signup ($STATUS)" \
  || bad "POST /auth/signup — got $STATUS :: $BODY"

req POST /auth/login "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}"
assert_status 200 "$STATUS" "POST /auth/login"
TOKEN="$(printf '%s' "$BODY" | json_get token)"
[[ -n "$TOKEN" ]] && ok "Got JWT token" || die "No token returned :: $BODY"

req GET /auth/me
assert_status 200 "$STATUS" "GET /auth/me (with token)"

# Unauth check
SAVED="$TOKEN"; TOKEN=""
req GET /tasks
[[ "$STATUS" == "401" || "$STATUS" == "403" ]] \
  && ok "GET /tasks rejects missing token ($STATUS)" \
  || bad "GET /tasks should be 401/403 — got $STATUS"
TOKEN="$SAVED"

# ── 3. Goals CRUD ────────────────────────────────────────────
say "Goals CRUD"
req POST /goals '{"title":"Run 5K","target_value":5,"current_value":0,"unit":"km","deadline":"2026-12-31"}'
assert_status 200 "$STATUS" "POST /goals"
GOAL_ID="$(printf '%s' "$BODY" | json_get id)"
[[ -n "$GOAL_ID" ]] && ok "Goal id=$GOAL_ID" || bad "no goal id :: $BODY"

req GET /goals
assert_status 200 "$STATUS" "GET /goals"

req PATCH "/goals/$GOAL_ID" '{"current_value":2.5}'
[[ "$STATUS" == "200" ]] && ok "PATCH /goals/{id}" || bad "PATCH /goals — $STATUS :: $BODY"

# ── 4. Tasks CRUD (with goal link) ───────────────────────────
say "Tasks CRUD"
TODAY="$(date +%Y-%m-%d)"
req POST /tasks "{\"title\":\"Morning run\",\"task_date\":\"$TODAY\",\"goal_id\":\"$GOAL_ID\"}"
assert_status 200 "$STATUS" "POST /tasks"
TASK_ID="$(printf '%s' "$BODY" | json_get id)"
[[ -n "$TASK_ID" ]] && ok "Task id=$TASK_ID"

req GET "/tasks?date=$TODAY"
assert_status 200 "$STATUS" "GET /tasks?date=$TODAY"

req PATCH "/tasks/$TASK_ID" '{"completed":true}'
[[ "$STATUS" == "200" ]] && ok "PATCH /tasks/{id} (complete)" || bad "PATCH /tasks — $STATUS"

req DELETE "/tasks/$TASK_ID"
[[ "$STATUS" == "200" || "$STATUS" == "204" ]] \
  && ok "DELETE /tasks/{id} ($STATUS)" \
  || bad "DELETE /tasks — $STATUS :: $BODY"

# ── 5. Habits CRUD + logs ────────────────────────────────────
say "Habits CRUD"
req POST /habits '{"name":"Drink water","target":8,"unit":"glasses","frequency":"daily"}'
assert_status 200 "$STATUS" "POST /habits"
HABIT_ID="$(printf '%s' "$BODY" | json_get id)"
[[ -n "$HABIT_ID" ]] && ok "Habit id=$HABIT_ID"

req POST "/habits/$HABIT_ID/logs" "{\"log_date\":\"$TODAY\",\"value\":8}"
[[ "$STATUS" == "200" || "$STATUS" == "201" ]] \
  && ok "POST /habits/{id}/logs ($STATUS)" \
  || bad "POST habit log — $STATUS :: $BODY"

req GET /habits
assert_status 200 "$STATUS" "GET /habits (with streak)"
STREAK="$(printf '%s' "$BODY" | python3 -c "import sys,json
d=json.loads(sys.stdin.read())
arr=d.get('habits',d) if isinstance(d,dict) else d
for h in arr:
  if h.get('id')=='$HABIT_ID': print(h.get('current_streak',0)); break" 2>/dev/null)"
[[ "${STREAK:-0}" -ge 1 ]] && ok "Streak computed (=$STREAK)" || bad "Streak not >=1 (got '$STREAK')"

req DELETE "/habits/$HABIT_ID"
[[ "$STATUS" == "200" || "$STATUS" == "204" ]] \
  && ok "DELETE /habits/{id} ($STATUS)" \
  || bad "DELETE /habits — $STATUS"

# ── 6. Bulk import: /import/local ────────────────────────────
say "Local import"
PAYLOAD=$(cat <<JSON
{
  "goals":[{"local_id":"g1","title":"Read 12 books","target_value":12,"current_value":1,"unit":"books"}],
  "tasks":[{"title":"Read 10 pages","task_date":"$TODAY","goal_local_id":"g1"}],
  "habits":[{"name":"Meditate","target":1,"unit":"session","frequency":"daily",
             "logs":[{"log_date":"$TODAY","value":1}]}]
}
JSON
)
req POST /import/local "$PAYLOAD"
assert_status 200 "$STATUS" "POST /import/local"
echo "    summary: $BODY" | head -c 300; echo

# Cleanup created goal
req DELETE "/goals/$GOAL_ID" >/dev/null 2>&1 || true

# ── Summary ──────────────────────────────────────────────────
echo
printf "\033[1mResults: \033[32m%d passed\033[0m, \033[31m%d failed\033[0m\n" "$PASS" "$FAIL"
[[ "$FAIL" -eq 0 ]] || exit 1
