#!/usr/bin/env bash
# test_session_watchdog.sh — session-watchdog.sh 단위 테스트
# 사용법: bash scripts/tests/test_session_watchdog.sh

set -euo pipefail

WORKSPACE="/home/jay/workspace"
TEST_DIR=$(mktemp -d)
PASSED=0
FAILED=0
TOTAL=0

cleanup() {
    rm -rf "$TEST_DIR"
}
trap cleanup EXIT

# ────────────────────────────────────────────
# 헬퍼 함수
# ────────────────────────────────────────────

assert_eq() {
    local test_name="$1" expected="$2" actual="$3"
    TOTAL=$((TOTAL + 1))
    if [[ "$expected" == "$actual" ]]; then
        echo "  ✓ ${test_name}"
        PASSED=$((PASSED + 1))
    else
        echo "  ✗ ${test_name} (expected: ${expected}, got: ${actual})"
        FAILED=$((FAILED + 1))
    fi
}

assert_contains() {
    local test_name="$1" expected="$2" actual="$3"
    TOTAL=$((TOTAL + 1))
    if echo "$actual" | grep -q "$expected"; then
        echo "  ✓ ${test_name}"
        PASSED=$((PASSED + 1))
    else
        echo "  ✗ ${test_name} (expected to contain: ${expected})"
        echo "    actual: ${actual}"
        FAILED=$((FAILED + 1))
    fi
}

assert_not_contains() {
    local test_name="$1" unexpected="$2" actual="$3"
    TOTAL=$((TOTAL + 1))
    if echo "$actual" | grep -q "$unexpected"; then
        echo "  ✗ ${test_name} (should NOT contain: ${unexpected})"
        echo "    actual: ${actual}"
        FAILED=$((FAILED + 1))
    else
        echo "  ✓ ${test_name}"
        PASSED=$((PASSED + 1))
    fi
}

skip_test() {
    local test_name="$1" reason="$2"
    TOTAL=$((TOTAL + 1))
    echo "  - SKIP: ${test_name}"
    echo "    사유: ${reason}"
    # SKIP은 PASSED로 카운트 (차단 없음)
    PASSED=$((PASSED + 1))
}

# ────────────────────────────────────────────
# 판정 로직 함수 (watchdog 핵심 로직을 격리 재현)
# ────────────────────────────────────────────

# 반환값: "pass" | "alive_heartbeat" | "alive_pid" | "stalled" | "escalation"
judge_task() {
    local timers_file="$1"
    local heartbeat_dir="$2"
    local events_dir="$3"
    local task_id="$4"

    local STALE_THRESHOLD=600
    local MAX_RETRY_DEFAULT=2
    local NOW
    NOW=$(date +%s)

    # 1. .done 파일 확인
    local done_file="${events_dir}/${task_id}.done"
    if [[ -f "$done_file" || -f "${done_file}.acked" || -f "${done_file}.clear" ]]; then
        echo "pass: .done 파일 존재 → 완료 처리 중, 재위임 스킵"
        return
    fi

    # 메타데이터 읽기
    local RETRY_COUNT MAX_RETRY
    RETRY_COUNT=$(jq -r --arg tid "$task_id" '.tasks[$tid].retry_count // 0' "$timers_file" 2>/dev/null)
    MAX_RETRY=$(jq -r --arg tid "$task_id" '.tasks[$tid].max_retry // '"$MAX_RETRY_DEFAULT"'' "$timers_file" 2>/dev/null)

    if ! [[ "$RETRY_COUNT" =~ ^[0-9]+$ ]]; then RETRY_COUNT=0; fi
    if ! [[ "$MAX_RETRY" =~ ^[0-9]+$ ]]; then MAX_RETRY=$MAX_RETRY_DEFAULT; fi

    # 2. PID 추적 — 테스트 환경에서는 시뮬레이션 불가 (항상 false)
    local PID_ALIVE=false

    # 3. heartbeat mtime 확인
    local heartbeat_file="${heartbeat_dir}/${task_id}.heartbeat"
    local HEARTBEAT_ALIVE=false
    if [[ -f "$heartbeat_file" ]]; then
        local HEARTBEAT_MTIME ELAPSED
        HEARTBEAT_MTIME=$(stat -c %Y "$heartbeat_file" 2>/dev/null || echo 0)
        ELAPSED=$((NOW - HEARTBEAT_MTIME))
        if [[ "$ELAPSED" -lt "$STALE_THRESHOLD" ]]; then
            HEARTBEAT_ALIVE=true
            echo "alive_heartbeat: heartbeat ${ELAPSED}s ago → alive"
            return
        fi
    fi

    # race condition 방지: status 재확인
    local CURRENT_STATUS
    CURRENT_STATUS=$(jq -r --arg tid "$task_id" '.tasks[$tid].status // "unknown"' "$timers_file" 2>/dev/null)
    if [[ "$CURRENT_STATUS" != "running" ]]; then
        echo "pass: status=${CURRENT_STATUS} (not running) → 스킵"
        return
    fi

    # race condition 방지: end_time 존재 확인
    local END_TIME
    END_TIME=$(jq -r --arg tid "$task_id" '.tasks[$tid].end_time // ""' "$timers_file" 2>/dev/null)
    if [[ -n "$END_TIME" && "$END_TIME" != "null" ]]; then
        echo "pass: end_time 존재 → 완료됨, 재위임 스킵"
        return
    fi

    # 4. stalled 판정
    if [[ "$RETRY_COUNT" -ge "$MAX_RETRY" ]]; then
        echo "escalation: retry_count=${RETRY_COUNT} >= max_retry=${MAX_RETRY} → 에스컬레이션"
        return
    fi

    echo "STALLED: PID 없음, heartbeat 10분+ 초과"
}

# running 태스크 추출 함수 (anu-direct 제외)
get_running_tasks() {
    local timers_file="$1"
    jq -r '.tasks | to_entries[] | select(.value.status == "running") | select(.value.team_id != "anu-direct") | .key' \
        "$timers_file" 2>/dev/null
}

# backoff 판정 함수 (Circuit Breaker 로직을 격리 재현)
check_backoff() {
    local heartbeat_dir="$1"
    local task_id="$2"
    local now="$3"
    local RAPID_REFAIL_WINDOW=180
    local BACKOFF_DURATION=1800

    local backoff_file="${heartbeat_dir}/${task_id}.backoff"
    if [[ ! -f "$backoff_file" ]]; then
        echo "no_backoff"
        return
    fi
    local backoff_expiry
    backoff_expiry=$(sed -n '2p' "$backoff_file" 2>/dev/null || echo 0)
    if ! [[ "$backoff_expiry" =~ ^[0-9]+$ ]]; then backoff_expiry=0; fi
    if [[ "$backoff_expiry" -gt 0 ]] && [[ "$now" -lt "$backoff_expiry" ]]; then
        echo "backoff_active: 잔여 $(( backoff_expiry - now ))s"
        return
    fi
    local last_redeleg
    last_redeleg=$(sed -n '1p' "$backoff_file" 2>/dev/null || echo 0)
    if ! [[ "$last_redeleg" =~ ^[0-9]+$ ]]; then last_redeleg=0; fi
    if [[ "$last_redeleg" -gt 0 ]] && [[ $(( now - last_redeleg )) -lt "$RAPID_REFAIL_WINDOW" ]]; then
        echo "rapid_refail: $(( now - last_redeleg ))s since last redelegation"
        return
    fi
    echo "backoff_expired"
}

# 초기 유예 기간 판정 함수
# 반환값: "grace_skip" | "proceed"
check_grace_period() {
    local timers_file="$1"
    local task_id="$2"
    local now="$3"
    local GRACE_PERIOD=600

    local START_TIME
    START_TIME=$(jq -r --arg tid "$task_id" '.tasks[$tid].start_time // ""' "$timers_file" 2>/dev/null)
    if [[ -z "$START_TIME" || "$START_TIME" == "null" ]]; then
        echo "proceed: start_time 없음"
        return
    fi
    local START_EPOCH
    START_EPOCH=$(date -d "$START_TIME" +%s 2>/dev/null || echo 0)
    if [[ "$START_EPOCH" -le 0 ]]; then
        echo "proceed: start_time 파싱 실패"
        return
    fi
    local ELAPSED=$((now - START_EPOCH))
    if [[ "$ELAPSED" -lt "$GRACE_PERIOD" ]]; then
        echo "grace_skip: 시작 ${ELAPSED}초 전 (유예 기간)"
        return
    fi
    echo "proceed: 시작 ${ELAPSED}초 전 (유예 만료)"
}

# cokacdir key 매핑 함수 (watchdog의 get_cokacdir_key와 동일)
test_get_cokacdir_key() {
    local team_id="$1"
    case "$team_id" in
        dev*-team)
            local role="${team_id%-team}"
            echo "COKACDIR_KEY_${role^^}"
            ;;
        anu-direct)
            echo "COKACDIR_KEY_ANU"
            ;;
        *)
            echo ""
            ;;
    esac
}

# 서브넘버링 계산 함수
calc_next_task_id() {
    local task_id="$1"
    local base=$(echo "$task_id" | sed 's/\.[0-9]*$//')
    local current_sub=$(echo "$task_id" | grep -o '\.[0-9]*$' | tr -d '.')
    if ! [[ "$current_sub" =~ ^[0-9]+$ ]]; then current_sub=1; fi
    local next_sub=$((current_sub + 1))
    echo "${base}.${next_sub}"
}

# ────────────────────────────────────────────
# TC1: heartbeat 갱신 후 watchdog → 재위임 안 함
# ────────────────────────────────────────────
echo ""
echo "TC1: heartbeat 갱신 직후 → alive 판정 (재위임 안 함)"

TC1_DIR="${TEST_DIR}/tc1"
mkdir -p "${TC1_DIR}/memory/heartbeats" "${TC1_DIR}/memory/events"

# task-timers.json 생성
cat > "${TC1_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc1-001": {
      "team_id": "dev1-team",
      "status": "running",
      "schedule_id": "sched-tc1",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

# heartbeat 방금 생성 (현재 시각)
date +%s > "${TC1_DIR}/memory/heartbeats/task-tc1-001.heartbeat"

RESULT=$(judge_task \
    "${TC1_DIR}/memory/task-timers.json" \
    "${TC1_DIR}/memory/heartbeats" \
    "${TC1_DIR}/memory/events" \
    "task-tc1-001")

assert_not_contains "TC1-1: STALLED 판정이 나오지 않아야 함" "STALLED" "$RESULT"
assert_contains      "TC1-2: alive_heartbeat 판정이어야 함"  "alive_heartbeat" "$RESULT"

# ────────────────────────────────────────────
# TC2: heartbeat 10분 초과 + .done 없음 + PID 없음 → stalled
# ────────────────────────────────────────────
echo ""
echo "TC2: heartbeat 15분 초과 → STALLED 판정"

TC2_DIR="${TEST_DIR}/tc2"
mkdir -p "${TC2_DIR}/memory/heartbeats" "${TC2_DIR}/memory/events"

cat > "${TC2_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc2-001": {
      "team_id": "dev1-team",
      "status": "running",
      "schedule_id": "sched-tc2",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

# heartbeat 파일 생성 후 mtime을 15분 전으로 설정
touch "${TC2_DIR}/memory/heartbeats/task-tc2-001.heartbeat"
FIFTEEN_AGO=$(date -d "15 minutes ago" +"%Y%m%d%H%M.%S")
touch -t "$FIFTEEN_AGO" "${TC2_DIR}/memory/heartbeats/task-tc2-001.heartbeat"

RESULT=$(judge_task \
    "${TC2_DIR}/memory/task-timers.json" \
    "${TC2_DIR}/memory/heartbeats" \
    "${TC2_DIR}/memory/events" \
    "task-tc2-001")

assert_contains "TC2-1: STALLED 판정이어야 함" "STALLED" "$RESULT"

# ────────────────────────────────────────────
# TC3: .done 존재 → 재위임 안 함
# ────────────────────────────────────────────
echo ""
echo "TC3: .done 파일 존재 → pass 판정"

TC3_DIR="${TEST_DIR}/tc3"
mkdir -p "${TC3_DIR}/memory/heartbeats" "${TC3_DIR}/memory/events"

cat > "${TC3_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc3-001": {
      "team_id": "dev1-team",
      "status": "running",
      "schedule_id": "sched-tc3",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

# .done 파일 생성
touch "${TC3_DIR}/memory/events/task-tc3-001.done"

RESULT=$(judge_task \
    "${TC3_DIR}/memory/task-timers.json" \
    "${TC3_DIR}/memory/heartbeats" \
    "${TC3_DIR}/memory/events" \
    "task-tc3-001")

assert_contains "TC3-1: pass 판정이어야 함"             "pass"         "$RESULT"
assert_contains "TC3-2: .done 존재 → pass 메시지 포함"  ".done 파일 존재" "$RESULT"
assert_not_contains "TC3-3: STALLED 판정이 없어야 함"   "STALLED"      "$RESULT"

# ────────────────────────────────────────────
# TC3a: .done.acked 존재 → 재위임 안 함
# ────────────────────────────────────────────
echo ""
echo "TC3a: .done.acked 파일 존재 → pass 판정"

TC3a_DIR="${TEST_DIR}/tc3a"
mkdir -p "${TC3a_DIR}/memory/heartbeats" "${TC3a_DIR}/memory/events"

cat > "${TC3a_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc3a-001": {
      "team_id": "dev1-team",
      "status": "running",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

touch "${TC3a_DIR}/memory/events/task-tc3a-001.done.acked"

RESULT=$(judge_task \
    "${TC3a_DIR}/memory/task-timers.json" \
    "${TC3a_DIR}/memory/heartbeats" \
    "${TC3a_DIR}/memory/events" \
    "task-tc3a-001")

assert_contains "TC3a-1: pass 판정이어야 함" "pass" "$RESULT"
assert_not_contains "TC3a-2: STALLED 판정이 없어야 함" "STALLED" "$RESULT"

# ────────────────────────────────────────────
# TC3b: status=completed → 재위임 안 함 (race condition 방어)
# ────────────────────────────────────────────
echo ""
echo "TC3b: status=completed → pass 판정 (race condition 방어)"

TC3b_DIR="${TEST_DIR}/tc3b"
mkdir -p "${TC3b_DIR}/memory/heartbeats" "${TC3b_DIR}/memory/events"

cat > "${TC3b_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc3b-001": {
      "team_id": "dev1-team",
      "status": "completed",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

RESULT=$(judge_task \
    "${TC3b_DIR}/memory/task-timers.json" \
    "${TC3b_DIR}/memory/heartbeats" \
    "${TC3b_DIR}/memory/events" \
    "task-tc3b-001")

assert_contains "TC3b-1: pass 판정이어야 함" "pass" "$RESULT"
assert_not_contains "TC3b-2: STALLED 판정이 없어야 함" "STALLED" "$RESULT"

# ────────────────────────────────────────────
# TC3c: end_time 존재 → 재위임 안 함 (timer end 완료)
# ────────────────────────────────────────────
echo ""
echo "TC3c: end_time 존재 → pass 판정 (timer end 완료)"

TC3c_DIR="${TEST_DIR}/tc3c"
mkdir -p "${TC3c_DIR}/memory/heartbeats" "${TC3c_DIR}/memory/events"

cat > "${TC3c_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc3c-001": {
      "team_id": "dev1-team",
      "status": "running",
      "end_time": "2026-04-16T10:30:00",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

RESULT=$(judge_task \
    "${TC3c_DIR}/memory/task-timers.json" \
    "${TC3c_DIR}/memory/heartbeats" \
    "${TC3c_DIR}/memory/events" \
    "task-tc3c-001")

assert_contains "TC3c-1: pass 판정이어야 함" "pass" "$RESULT"
assert_not_contains "TC3c-2: STALLED 판정이 없어야 함" "STALLED" "$RESULT"

# ────────────────────────────────────────────
# TC4: PID 존재 + heartbeat 10분 초과 → alive (SKIP)
# ────────────────────────────────────────────
echo ""
echo "TC4: PID 추적 테스트 (SKIP)"

skip_test \
    "TC4: PID 존재 시 alive 판정" \
    "PID 추적 로직이 system_prompt 파일 경로(/home/jay/.cokacdir/system_prompt_*)와 ps aux 결과에 의존하여 테스트 환경에서 재현 불가. 실제 Claude Code 세션이 실행 중인 환경에서만 검증 가능."

# ────────────────────────────────────────────
# TC5: retry_count=2 (max 초과) → 에스컬레이션 (dispatch.py 호출 안 함)
# ────────────────────────────────────────────
echo ""
echo "TC5: retry_count >= max_retry → 에스컬레이션 (dispatch.py 차단)"

TC5_DIR="${TEST_DIR}/tc5"
mkdir -p "${TC5_DIR}/memory/heartbeats" "${TC5_DIR}/memory/events"

cat > "${TC5_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc5-001": {
      "team_id": "dev1-team",
      "status": "running",
      "schedule_id": "sched-tc5",
      "retry_count": 2,
      "max_retry": 2
    }
  }
}
JSON

# heartbeat 없음 (stalled 조건 충족)

RESULT=$(judge_task \
    "${TC5_DIR}/memory/task-timers.json" \
    "${TC5_DIR}/memory/heartbeats" \
    "${TC5_DIR}/memory/events" \
    "task-tc5-001")

assert_contains     "TC5-1: 에스컬레이션 메시지 포함"       "에스컬레이션"  "$RESULT"
assert_contains     "TC5-2: retry_count 정보 포함"            "retry_count=2"  "$RESULT"
assert_not_contains "TC5-3: STALLED dispatch 로직 진입 안 함" "STALLED: PID"   "$RESULT"

# retry_count가 max_retry보다 큰 경우도 검증
cat > "${TC5_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc5-002": {
      "team_id": "dev1-team",
      "status": "running",
      "schedule_id": "sched-tc5b",
      "retry_count": 3,
      "max_retry": 2
    }
  }
}
JSON

RESULT2=$(judge_task \
    "${TC5_DIR}/memory/task-timers.json" \
    "${TC5_DIR}/memory/heartbeats" \
    "${TC5_DIR}/memory/events" \
    "task-tc5-002")

assert_contains "TC5-4: retry_count > max_retry도 에스컬레이션" "에스컬레이션" "$RESULT2"

# ────────────────────────────────────────────
# TC6: anu-direct 작업 → watchdog 대상 제외
# ────────────────────────────────────────────
echo ""
echo "TC6: anu-direct 태스크 → 검사 대상 제외"

TC6_DIR="${TEST_DIR}/tc6"
mkdir -p "${TC6_DIR}/memory"

cat > "${TC6_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc6-anu": {
      "team_id": "anu-direct",
      "status": "running",
      "retry_count": 0,
      "max_retry": 2
    },
    "task-tc6-dev": {
      "team_id": "dev1-team",
      "status": "running",
      "retry_count": 0,
      "max_retry": 2
    },
    "task-tc6-done": {
      "team_id": "dev2-team",
      "status": "done",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

RUNNING=$(get_running_tasks "${TC6_DIR}/memory/task-timers.json")

assert_not_contains "TC6-1: anu-direct 태스크가 검사 목록에 없어야 함" "task-tc6-anu"  "$RUNNING"
assert_contains     "TC6-2: dev1-team 태스크는 검사 목록에 있어야 함"  "task-tc6-dev"  "$RUNNING"
assert_not_contains "TC6-3: done 상태 태스크는 검사 목록에 없어야 함"  "task-tc6-done" "$RUNNING"

# anu-direct만 있으면 running 목록이 비어야 함
cat > "${TC6_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc6-anu2": {
      "team_id": "anu-direct",
      "status": "running",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

RUNNING2=$(get_running_tasks "${TC6_DIR}/memory/task-timers.json")
assert_eq "TC6-4: anu-direct만 있으면 running 목록이 비어야 함" "" "$RUNNING2"

# ────────────────────────────────────────────
# TC7: post-tool-use.sh 기존 기능 회귀
# ────────────────────────────────────────────
echo ""
echo "TC7: post-tool-use.sh 회귀 검사"

POST_TOOL_SCRIPT="/home/jay/.claude/hooks/post-tool-use.sh"

# 문법 검사
if bash -n "$POST_TOOL_SCRIPT" 2>/tmp/tc7_syntax_err; then
    SYNTAX_OK="ok"
else
    SYNTAX_OK="fail: $(cat /tmp/tc7_syntax_err)"
fi
assert_eq "TC7-1: post-tool-use.sh 문법 오류 없음" "ok" "$SYNTAX_OK"

# heartbeat 갱신 행 존재 확인
HEARTBEAT_LINE=$(grep -n "heartbeat" "$POST_TOOL_SCRIPT" 2>/dev/null | head -5)
assert_contains "TC7-2: heartbeat 갱신 코드 존재" "heartbeat" "$HEARTBEAT_LINE"

# 구체적인 heartbeat 갱신 패턴 확인 (date +%s > *.heartbeat)
HEARTBEAT_WRITE=$(grep "date +%s.*heartbeat" "$POST_TOOL_SCRIPT" 2>/dev/null || echo "")
assert_contains "TC7-3: date +%s > *.heartbeat 패턴 존재" "date +%s" "$HEARTBEAT_WRITE"

# TASK_ID 가드 확인 (unknown일 때 heartbeat 쓰지 않음)
TASKID_GUARD=$(grep 'TASK_ID.*unknown.*heartbeat\|heartbeat.*TASK_ID.*unknown' "$POST_TOOL_SCRIPT" 2>/dev/null || \
               grep -A1 'heartbeat' "$POST_TOOL_SCRIPT" | grep -v heartbeat | head -3 || echo "")
# [[ -n "$TASK_ID" && "$TASK_ID" != "unknown" ]] 패턴 검증
GUARD_LINE=$(grep 'TASK_ID.*!=.*unknown.*heartbeat\|\[\[.*TASK_ID.*heartbeat' "$POST_TOOL_SCRIPT" 2>/dev/null || \
             grep -B0 'heartbeat' "$POST_TOOL_SCRIPT" | head -3)
assert_contains "TC7-4: heartbeat 갱신 전 TASK_ID 가드 존재" "TASK_ID" "$GUARD_LINE"

# ────────────────────────────────────────────
# TC8: systemd timer 상태 확인
# ────────────────────────────────────────────
echo ""
echo "TC8: systemd timer 상태 확인"

# is-enabled 확인
TIMER_ENABLED=$(systemctl --user is-enabled session-watchdog.timer 2>/dev/null || echo "not-found")
assert_eq "TC8-1: session-watchdog.timer enabled" "enabled" "$TIMER_ENABLED"

# is-active 확인
TIMER_ACTIVE=$(systemctl --user is-active session-watchdog.timer 2>/dev/null || echo "not-found")
assert_eq "TC8-2: session-watchdog.timer active" "active" "$TIMER_ACTIVE"

# ────────────────────────────────────────────
# TC9: 빠른 재실패 → backoff 동작 확인
# ────────────────────────────────────────────
echo ""
echo "TC9: 빠른 재실패 → backoff 동작 확인"

TC9_DIR="${TEST_DIR}/tc9"
mkdir -p "${TC9_DIR}/memory/heartbeats"

NOW9=$(date +%s)

# 시나리오 A: 60초 전에 재위임 → 빠른 재실패 감지
LAST_REDELEG=$(( NOW9 - 60 ))
echo "$LAST_REDELEG" > "${TC9_DIR}/memory/heartbeats/task-tc9-001.backoff"
echo "0" >> "${TC9_DIR}/memory/heartbeats/task-tc9-001.backoff"

RESULT=$(check_backoff "${TC9_DIR}/memory/heartbeats" "task-tc9-001" "$NOW9")
assert_contains "TC9-1: 60초 전 재위임 → rapid_refail 감지" "rapid_refail" "$RESULT"

# 시나리오 B: 300초 전에 재위임 (180초 초과) → 만료
LAST_REDELEG2=$(( NOW9 - 300 ))
echo "$LAST_REDELEG2" > "${TC9_DIR}/memory/heartbeats/task-tc9-002.backoff"
echo "0" >> "${TC9_DIR}/memory/heartbeats/task-tc9-002.backoff"

RESULT2=$(check_backoff "${TC9_DIR}/memory/heartbeats" "task-tc9-002" "$NOW9")
assert_contains "TC9-2: 300초 전 재위임 → backoff_expired" "backoff_expired" "$RESULT2"

# 시나리오 C: .backoff 파일 없음 → no_backoff
RESULT3=$(check_backoff "${TC9_DIR}/memory/heartbeats" "task-tc9-003" "$NOW9")
assert_contains "TC9-3: backoff 파일 없음 → no_backoff" "no_backoff" "$RESULT3"

# ────────────────────────────────────────────
# TC10: 동시 재위임 상한(3건) 초과 → 4번째 스킵
# ────────────────────────────────────────────
echo ""
echo "TC10: 동시 재위임 상한(3건) 초과 → 4번째 스킵"

MAX_REDELEGATIONS_PER_CYCLE=3
REDELEG_COUNTER=0
DEFERRED=()

for i in 1 2 3 4 5; do
    if [[ "$REDELEG_COUNTER" -ge "$MAX_REDELEGATIONS_PER_CYCLE" ]]; then
        DEFERRED+=("task-tc10-${i}")
    else
        REDELEG_COUNTER=$((REDELEG_COUNTER + 1))
    fi
done

assert_eq   "TC10-1: 재위임 실행 건수 = 3" "3" "$REDELEG_COUNTER"
assert_eq   "TC10-2: 연기 건수 = 2" "2" "${#DEFERRED[@]}"
assert_contains "TC10-3: 4번째 태스크 연기됨" "task-tc10-4" "${DEFERRED[*]}"
assert_contains "TC10-4: 5번째 태스크 연기됨" "task-tc10-5" "${DEFERRED[*]}"

# ────────────────────────────────────────────
# TC11: backoff 만료 후 재위임 재개
# ────────────────────────────────────────────
echo ""
echo "TC11: backoff 만료 후 재위임 재개"

TC11_DIR="${TEST_DIR}/tc11"
mkdir -p "${TC11_DIR}/memory/heartbeats"

NOW11=$(date +%s)

# 시나리오 A: 백오프 아직 유효 (만료 = NOW + 600)
BACKOFF_UNTIL=$(( NOW11 + 600 ))
echo "$(( NOW11 - 1000 ))" > "${TC11_DIR}/memory/heartbeats/task-tc11-001.backoff"
echo "$BACKOFF_UNTIL" >> "${TC11_DIR}/memory/heartbeats/task-tc11-001.backoff"

RESULT_A=$(check_backoff "${TC11_DIR}/memory/heartbeats" "task-tc11-001" "$NOW11")
assert_contains "TC11-1: 백오프 유효 → backoff_active" "backoff_active" "$RESULT_A"

# 시나리오 B: 백오프 만료 (만료 = NOW - 100)
BACKOFF_PAST=$(( NOW11 - 100 ))
echo "$(( NOW11 - 2000 ))" > "${TC11_DIR}/memory/heartbeats/task-tc11-002.backoff"
echo "$BACKOFF_PAST" >> "${TC11_DIR}/memory/heartbeats/task-tc11-002.backoff"

RESULT_B=$(check_backoff "${TC11_DIR}/memory/heartbeats" "task-tc11-002" "$NOW11")
assert_contains "TC11-2: 백오프 만료 → backoff_expired" "backoff_expired" "$RESULT_B"

# 시나리오 C: .backoff 파일 없음 → no_backoff
RESULT_C=$(check_backoff "${TC11_DIR}/memory/heartbeats" "task-tc11-003" "$NOW11")
assert_contains "TC11-3: backoff 파일 없음 → no_backoff" "no_backoff" "$RESULT_C"

# ────────────────────────────────────────────
# TC12: 초기 유예 기간 (dispatch 후 10분 이내 스킵)
# ────────────────────────────────────────────
echo ""
echo "TC12: 초기 유예 기간 (dispatch 후 10분 이내 스킵)"

TC12_DIR="${TEST_DIR}/tc12"
mkdir -p "${TC12_DIR}/memory"

NOW12=$(date +%s)

# 시나리오 A: 3분 전 시작 → grace_skip
THREE_MIN_AGO=$(date -d "@$((NOW12 - 180))" +"%Y-%m-%dT%H:%M:%S" 2>/dev/null)
cat > "${TC12_DIR}/memory/task-timers.json" <<JSON
{
  "tasks": {
    "task-tc12-001": {
      "team_id": "dev1-team",
      "status": "running",
      "start_time": "${THREE_MIN_AGO}",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

RESULT=$(check_grace_period "${TC12_DIR}/memory/task-timers.json" "task-tc12-001" "$NOW12")
assert_contains "TC12-1: 3분 전 시작 → grace_skip" "grace_skip" "$RESULT"

# 시나리오 B: 15분 전 시작 → proceed
FIFTEEN_MIN_AGO=$(date -d "@$((NOW12 - 900))" +"%Y-%m-%dT%H:%M:%S" 2>/dev/null)
cat > "${TC12_DIR}/memory/task-timers.json" <<JSON
{
  "tasks": {
    "task-tc12-002": {
      "team_id": "dev1-team",
      "status": "running",
      "start_time": "${FIFTEEN_MIN_AGO}",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

RESULT2=$(check_grace_period "${TC12_DIR}/memory/task-timers.json" "task-tc12-002" "$NOW12")
assert_contains "TC12-2: 15분 전 시작 → proceed" "proceed" "$RESULT2"

# 시나리오 C: start_time 없음 → proceed
cat > "${TC12_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-tc12-003": {
      "team_id": "dev1-team",
      "status": "running",
      "retry_count": 0,
      "max_retry": 2
    }
  }
}
JSON

RESULT3=$(check_grace_period "${TC12_DIR}/memory/task-timers.json" "task-tc12-003" "$NOW12")
assert_contains "TC12-3: start_time 없음 → proceed" "proceed" "$RESULT3"

# ────────────────────────────────────────────
# TC13: 재위임 전 /stop 정리 (cleanup)
# ────────────────────────────────────────────
echo ""
echo "TC13: 재위임 전 /stop 정리 (cleanup)"

# 매핑 테스트
RESULT=$(test_get_cokacdir_key "dev1-team")
assert_eq "TC13-1: dev1-team → COKACDIR_KEY_DEV1" "COKACDIR_KEY_DEV1" "$RESULT"

RESULT2=$(test_get_cokacdir_key "dev2-team")
assert_eq "TC13-2: dev2-team → COKACDIR_KEY_DEV2" "COKACDIR_KEY_DEV2" "$RESULT2"

RESULT3=$(test_get_cokacdir_key "anu-direct")
assert_eq "TC13-3: anu-direct → COKACDIR_KEY_ANU" "COKACDIR_KEY_ANU" "$RESULT3"

RESULT4=$(test_get_cokacdir_key "unknown-team")
assert_eq "TC13-4: unknown-team → 빈 문자열" "" "$RESULT4"

# session-watchdog.sh에 cleanup 코드 존재 확인
WATCHDOG_SCRIPT="/home/jay/workspace/scripts/session-watchdog.sh"
CRON_REMOVE=$(grep "cron-remove" "$WATCHDOG_SCRIPT" 2>/dev/null || echo "")
assert_contains "TC13-5: cron-remove 코드 존재" "cron-remove" "$CRON_REMOVE"

TIMER_END=$(grep "task-timer\.py.*end" "$WATCHDOG_SCRIPT" 2>/dev/null || echo "")
assert_contains "TC13-6: task-timer.py end 코드 존재" "task-timer.py" "$TIMER_END"

# ────────────────────────────────────────────
# TC14: 재위임 서브넘버링 (task-1736.1 → task-1736.2)
# ────────────────────────────────────────────
echo ""
echo "TC14: 재위임 서브넘버링 (task-1736.1 → task-1736.2)"

RESULT=$(calc_next_task_id "task-1736.1")
assert_eq "TC14-1: task-1736.1 → task-1736.2" "task-1736.2" "$RESULT"

RESULT2=$(calc_next_task_id "task-1736.5")
assert_eq "TC14-2: task-1736.5 → task-1736.6" "task-1736.6" "$RESULT2"

RESULT3=$(calc_next_task_id "task-100.1")
assert_eq "TC14-3: task-100.1 → task-100.2" "task-100.2" "$RESULT3"

RESULT4=$(calc_next_task_id "task-999.99")
assert_eq "TC14-4: task-999.99 → task-999.100" "task-999.100" "$RESULT4"

# ────────────────────────────────────────────
# 보너스: jq 쿼리 정확성 검증
# ────────────────────────────────────────────
echo ""
echo "BONUS: jq 쿼리 정확성 검증"

BONUS_DIR="${TEST_DIR}/bonus"
mkdir -p "${BONUS_DIR}/memory"

cat > "${BONUS_DIR}/memory/task-timers.json" <<'JSON'
{
  "tasks": {
    "task-b001": {
      "team_id": "dev1-team",
      "status": "running",
      "schedule_id": "sched-b001",
      "retry_count": 1,
      "max_retry": 3,
      "task_file": "/home/jay/workspace/memory/tasks/task-b001.md"
    },
    "task-b002": {
      "team_id": "anu-direct",
      "status": "running"
    },
    "task-b003": {
      "team_id": "dev2-team",
      "status": "done"
    }
  }
}
JSON

# schedule_id 추출
SCHED=$(jq -r --arg tid "task-b001" '.tasks[$tid].schedule_id // ""' \
    "${BONUS_DIR}/memory/task-timers.json")
assert_eq "BONUS-1: schedule_id 추출 정확도" "sched-b001" "$SCHED"

# retry_count 추출
RC=$(jq -r --arg tid "task-b001" '.tasks[$tid].retry_count // 0' \
    "${BONUS_DIR}/memory/task-timers.json")
assert_eq "BONUS-2: retry_count 추출 정확도" "1" "$RC"

# max_retry 추출
MR=$(jq -r --arg tid "task-b001" '.tasks[$tid].max_retry // 2' \
    "${BONUS_DIR}/memory/task-timers.json")
assert_eq "BONUS-3: max_retry 추출 정확도" "3" "$MR"

# task_file 추출
TF=$(jq -r --arg tid "task-b001" '.tasks[$tid].task_file // ""' \
    "${BONUS_DIR}/memory/task-timers.json")
assert_eq "BONUS-4: task_file 추출 정확도" "/home/jay/workspace/memory/tasks/task-b001.md" "$TF"

# running + anu-direct 제외 카운트
RUN_COUNT=$(jq '[.tasks | to_entries[] | select(.value.status == "running") | select(.value.team_id != "anu-direct")] | length' \
    "${BONUS_DIR}/memory/task-timers.json")
assert_eq "BONUS-5: running 태스크 수 (anu-direct 제외)" "1" "$RUN_COUNT"

# ────────────────────────────────────────────
# 결과 출력
# ────────────────────────────────────────────
echo ""
echo "========================================"
echo "결과: ${PASSED}/${TOTAL} (${FAILED}건 실패)"
echo "========================================"

if [[ "$FAILED" -gt 0 ]]; then
    exit 1
else
    exit 0
fi
