#!/usr/bin/env bash
# session-watchdog.sh — 팀장 봇 세션 자동 이어가기 Watchdog
# systemd timer에 의해 2분마다 실행

set -euo pipefail

WORKSPACE="/home/jay/workspace"
TIMERS_FILE="${WORKSPACE}/memory/task-timers.json"
HEARTBEAT_DIR="${WORKSPACE}/memory/heartbeats"
EVENTS_DIR="${WORKSPACE}/memory/events"
LOG_FILE="${WORKSPACE}/logs/session-watchdog.log"
STALE_THRESHOLD=600  # 10분 (초)
GRACE_PERIOD=600             # dispatch 직후 유예 기간 (초)
WATCHDOG_CHAT_ID="6937032012"
MAX_RETRY_DEFAULT=2
BACKOFF_DURATION=1800        # 30분 백오프 (초)
RAPID_REFAIL_WINDOW=180      # 3분 이내 재stall → 빠른 재실패
MAX_REDELEGATIONS_PER_CYCLE=3  # 사이클당 최대 재위임 건수

# 환경변수에서 Telegram 토큰 로드 (ps aux 노출 방지)
source "${WORKSPACE}/.env.keys"

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

# team_id → cokacdir key 매핑
get_cokacdir_key() {
    local team_id="$1"
    case "$team_id" in
        dev*-team)
            local role="${team_id%-team}"  # dev1-team → dev1
            local var="COKACDIR_KEY_${role^^}"  # COKACDIR_KEY_DEV1
            echo "${!var}"
            ;;
        anu-direct)
            echo "${COKACDIR_KEY_ANU:-}"
            ;;
        *)
            echo ""
            ;;
    esac
}

# task-timers.json이 없으면 종료
if [[ ! -f "$TIMERS_FILE" ]]; then
    log "task-timers.json 없음, 종료"
    exit 0
fi

# jq 필수
if ! command -v jq &>/dev/null; then
    log "ERROR: jq가 설치되어 있지 않습니다"
    exit 1
fi

# running 상태 tasks 추출 (anu-direct 제외)
# jq로 tasks에서 status=running인 것만 추출
RUNNING_TASKS=$(jq -r '.tasks | to_entries[] | select(.value.status == "running") | select(.value.team_id != "anu-direct") | .key' "$TIMERS_FILE" 2>/dev/null)

if [[ -z "$RUNNING_TASKS" ]]; then
    log "running 태스크 없음"
    exit 0
fi

NOW=$(date +%s)
REDELEGATION_COUNT=0
STALLED_LIST=()
REDELEGATED_LIST=()
ESCALATED_LIST=()

while IFS= read -r TASK_ID; do
    [[ -z "$TASK_ID" ]] && continue

    log "검사 시작: ${TASK_ID}"

    # 1. .done 파일 존재 확인
    DONE_FILE="${EVENTS_DIR}/${TASK_ID}.done"
    if [[ -f "$DONE_FILE" || -f "${DONE_FILE}.acked" || -f "${DONE_FILE}.clear" ]]; then
        log "${TASK_ID}: .done 파일 존재 → 완료 처리 중, 재위임 스킵"
        continue
    fi

    # 1.1 재시도 계열 완료 체크 (base task의 형제 +N 중 하나라도 .done이면 스킵)
    BASE_TASK_ID=$(echo "$TASK_ID" | sed 's/+[0-9]*$//')
    if [[ "$BASE_TASK_ID" != "$TASK_ID" ]]; then
        SIBLING_DONE=false
        for DONE_CHECK in "${EVENTS_DIR}/${BASE_TASK_ID}"*.done "${EVENTS_DIR}/${BASE_TASK_ID}"*.done.acked "${EVENTS_DIR}/${BASE_TASK_ID}"*.done.clear; do
            if [[ -f "$DONE_CHECK" ]]; then
                SIBLING_DONE=true
                log "${TASK_ID}: 형제 작업 완료 감지 ($(basename "$DONE_CHECK")) → 재위임 스킵"
                break
            fi
        done
        if [[ "$SIBLING_DONE" == "true" ]]; then
            python3 "${WORKSPACE}/memory/task-timer.py" end "$TASK_ID" 2>/dev/null || true
            continue
        fi
    fi

    # 1.5. 초기 유예 기간 확인 (dispatch 직후 10분 이내 → 스킵)
    START_TIME=$(jq -r --arg tid "$TASK_ID" '.tasks[$tid].start_time // ""' "$TIMERS_FILE" 2>/dev/null)
    if [[ -n "$START_TIME" && "$START_TIME" != "null" ]]; then
        START_EPOCH=$(date -d "$START_TIME" +%s 2>/dev/null || echo 0)
        if [[ "$START_EPOCH" -gt 0 ]]; then
            GRACE_ELAPSED=$((NOW - START_EPOCH))
            if [[ "$GRACE_ELAPSED" -lt "$GRACE_PERIOD" ]]; then
                log "${TASK_ID}: 시작 ${GRACE_ELAPSED}초 전 (유예 기간 ${GRACE_PERIOD}초) → 스킵"
                continue
            fi
        fi
    fi

    # task 메타데이터 읽기
    SCHEDULE_ID=$(jq -r --arg tid "$TASK_ID" '.tasks[$tid].schedule_id // ""' "$TIMERS_FILE" 2>/dev/null)
    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)
    TASK_FILE=$(jq -r --arg tid "$TASK_ID" '.tasks[$tid].task_file // ""' "$TIMERS_FILE" 2>/dev/null)
    TEAM_ID=$(jq -r --arg tid "$TASK_ID" '.tasks[$tid].team_id // ""' "$TIMERS_FILE" 2>/dev/null)

    # retry_count가 숫자가 아닐 경우 0으로 초기화
    if ! [[ "$RETRY_COUNT" =~ ^[0-9]+$ ]]; then
        RETRY_COUNT=0
    fi
    if ! [[ "$MAX_RETRY" =~ ^[0-9]+$ ]]; then
        MAX_RETRY=$MAX_RETRY_DEFAULT
    fi

    # 2. PID 추적 (Primary)
    PID_ALIVE=false
    if [[ -n "$SCHEDULE_ID" && "$SCHEDULE_ID" != "null" ]]; then
        PROMPT_FILE=$(grep -rl "$SCHEDULE_ID" /home/jay/.cokacdir/system_prompt_* 2>/dev/null | head -1)
        if [[ -n "$PROMPT_FILE" ]]; then
            HASH=$(basename "$PROMPT_FILE" | sed 's/system_prompt_\(.*\)_.*/\1/')
            PID=$(ps aux | grep "system_prompt_${HASH}" | grep -v grep | awk '{print $2}' | head -1)
            if [[ -n "$PID" ]]; then
                PID_ALIVE=true
                log "${TASK_ID}: PID ${PID} 존재 → alive"
            fi
        fi
    fi

    if [[ "$PID_ALIVE" == "true" ]]; then
        continue
    fi

    # 3. heartbeat mtime 확인 (Secondary)
    HEARTBEAT_FILE="${HEARTBEAT_DIR}/${TASK_ID}.heartbeat"
    HEARTBEAT_ALIVE=false
    if [[ -f "$HEARTBEAT_FILE" ]]; then
        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
            log "${TASK_ID}: heartbeat ${ELAPSED}s ago (< ${STALE_THRESHOLD}s) → alive"
        else
            log "${TASK_ID}: heartbeat ${ELAPSED}s ago (>= ${STALE_THRESHOLD}s) → stale"
        fi
    else
        log "${TASK_ID}: heartbeat 파일 없음"
    fi

    if [[ "$HEARTBEAT_ALIVE" == "true" ]]; then
        continue
    fi

    # 4. stalled 판정
    # race condition 방지: status 재확인
    CURRENT_STATUS=$(jq -r --arg tid "$TASK_ID" '.tasks[$tid].status // "unknown"' "$TIMERS_FILE" 2>/dev/null)
    if [[ "$CURRENT_STATUS" != "running" ]]; then
        log "${TASK_ID}: status=${CURRENT_STATUS} (not running) → 스킵"
        continue
    fi

    # race condition 방지: 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
        log "${TASK_ID}: end_time 존재 → 완료됨, 재위임 스킵"
        continue
    fi

    # 재시도 계열: base task의 end_time 존재 시에도 완료 처리
    BASE_TASK_ID_END=$(echo "$TASK_ID" | sed 's/+[0-9]*$//')
    if [[ "$BASE_TASK_ID_END" != "$TASK_ID" ]]; then
        BASE_END_TIME=$(jq -r --arg tid "$BASE_TASK_ID_END" '.tasks[$tid].end_time // ""' "$TIMERS_FILE" 2>/dev/null)
        if [[ -n "$BASE_END_TIME" && "$BASE_END_TIME" != "null" ]]; then
            log "${TASK_ID}: base task(${BASE_TASK_ID_END}) end_time 존재 → 완료됨, 재위임 스킵"
            python3 "${WORKSPACE}/memory/task-timer.py" end "$TASK_ID" 2>/dev/null || true
            continue
        fi
    fi

    log "${TASK_ID}: STALLED 판정 (PID 없음, heartbeat 10분+ 초과)"

    # --- Circuit Breaker: 백오프 체크 ---
    BACKOFF_FILE="${HEARTBEAT_DIR}/${TASK_ID}.backoff"
    if [[ -f "$BACKOFF_FILE" ]]; then
        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
            REMAINING=$(( BACKOFF_EXPIRY - NOW ))
            log "${TASK_ID}: 백오프 중 (잔여 ${REMAINING}s), 재위임 스킵"
            STALLED_LIST+=("${TASK_ID}(${TEAM_ID},backoff)")
            continue
        fi
        # 백오프 만료 또는 미적용 — 빠른 재실패 감지
        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
            # 빠른 재실패: 30분 백오프 설정
            BACKOFF_UNTIL=$(( NOW + BACKOFF_DURATION ))
            echo "${LAST_REDELEG}" > "$BACKOFF_FILE"
            echo "${BACKOFF_UNTIL}" >> "$BACKOFF_FILE"
            log "${TASK_ID}: 빠른 재실패 감지 (${RAPID_REFAIL_WINDOW}s 이내), 30분 백오프 설정"
            STALLED_LIST+=("${TASK_ID}(${TEAM_ID},rapid-refail)")
            continue
        fi
    fi

    # 글로벌 재시도 상한: base task 기준으로 전체 +N 합산
    BASE_TASK_ID_FOR_RETRY=$(echo "$TASK_ID" | sed 's/+[0-9]*$//')
    GLOBAL_RETRY_COUNT=0
    while IFS= read -r SIBLING_TID; do
        [[ -z "$SIBLING_TID" ]] && continue
        S_RC=$(jq -r --arg tid "$SIBLING_TID" '.tasks[$tid].retry_count // 0' "$TIMERS_FILE" 2>/dev/null)
        if [[ "$S_RC" =~ ^[0-9]+$ ]]; then
            GLOBAL_RETRY_COUNT=$((GLOBAL_RETRY_COUNT + S_RC + 1))
        fi
    done <<< "$(jq -r '.tasks | keys[]' "$TIMERS_FILE" 2>/dev/null | grep "^${BASE_TASK_ID_FOR_RETRY}")"

    GLOBAL_MAX_RETRY=3
    if [[ "$GLOBAL_RETRY_COUNT" -ge "$GLOBAL_MAX_RETRY" ]]; then
        log "${TASK_ID}: 글로벌 재시도 상한 (${GLOBAL_RETRY_COUNT}/${GLOBAL_MAX_RETRY}) 도달 → 재위임 거부"
        STALLED_LIST+=("${TASK_ID}(${TEAM_ID},global-retry-limit)")
        continue
    fi

    if [[ "$RETRY_COUNT" -ge "$MAX_RETRY" ]]; then
        log "${TASK_ID}: retry_count=${RETRY_COUNT} >= max_retry=${MAX_RETRY} → 에스컬레이션"
        ESCALATED_LIST+=("${TASK_ID}(${TEAM_ID})")
        continue
    fi

    # 동시 재위임 상한 체크
    if [[ "$REDELEGATION_COUNT" -ge "$MAX_REDELEGATIONS_PER_CYCLE" ]]; then
        log "${TASK_ID}: 이번 사이클 재위임 상한(${MAX_REDELEGATIONS_PER_CYCLE}) 도달, 다음 사이클로 연기"
        STALLED_LIST+=("${TASK_ID}(${TEAM_ID},deferred)")
        continue
    fi

    # 재위임 비활성화 — 경고만 전송 (2026-04-16 제이회장님 지시)
    log "${TASK_ID}: STALLED 감지 — 재위임 비활성화, 경고만 전송"
    STALLED_LIST+=("${TASK_ID}(${TEAM_ID},stalled-alert-only)")

    # dispatch.py 재위임 호출 주석 처리 (향후 재활성화 가능)
    if [[ -f "$TASK_FILE" ]]; then
        # --- 재위임 전 정리 (유지) ---
        : # (정리 블록 비활성화됨 — 하단 주석 참조)
        # [비활성화] cokacdir 스케줄 제거 (2026-04-16 제이회장님 지시)
        # # 1) cokacdir 스케줄 제거
        # COKACDIR_KEY=$(get_cokacdir_key "$TEAM_ID")
        # if [[ -n "$SCHEDULE_ID" && "$SCHEDULE_ID" != "null" && -n "$COKACDIR_KEY" ]]; then
        #     log "${TASK_ID}: cokacdir 스케줄 제거 시도 (${SCHEDULE_ID})"
        #     /usr/local/bin/cokacdir --cron-remove "$SCHEDULE_ID" --chat "$WATCHDOG_CHAT_ID" --key "$COKACDIR_KEY" >> "$LOG_FILE" 2>&1 || true
        # fi

        # [비활성화] 잔존 프로세스 kill (2026-04-16 제이회장님 지시)
        # # 2) 잔존 프로세스 kill
        # if [[ -n "$SCHEDULE_ID" && "$SCHEDULE_ID" != "null" ]]; then
        #     STALE_PROMPT_FILE=$(grep -rl "$SCHEDULE_ID" /home/jay/.cokacdir/system_prompt_* 2>/dev/null | head -1)
        #     if [[ -n "$STALE_PROMPT_FILE" ]]; then
        #         STALE_HASH=$(basename "$STALE_PROMPT_FILE" | sed 's/system_prompt_\(.*\)_.*/\1/')
        #         STALE_PID=$(ps aux | grep "system_prompt_${STALE_HASH}" | grep -v grep | awk '{print $2}' | head -1)
        #         if [[ -n "$STALE_PID" ]]; then
        #             log "${TASK_ID}: 잔존 프로세스 kill (PID=${STALE_PID})"
        #             kill "$STALE_PID" 2>/dev/null || true
        #         fi
        #     fi
        # fi

        # [비활성화] task-timer end (2026-04-16 제이회장님 지시)
        # # 3) task-timer end 호출
        # log "${TASK_ID}: task-timer end 호출"
        # /usr/bin/python3 "${WORKSPACE}/memory/task-timer.py" end "$TASK_ID" >> "$LOG_FILE" 2>&1 || true

        # [비활성화] dispatch.py 재위임 (2026-04-16 제이회장님 지시)
        # NEW_RETRY=$((RETRY_COUNT + 1))
        # log "${TASK_ID}: 재위임 시도 (${NEW_RETRY}/${MAX_RETRY})"
        # # retry_count 업데이트
        # LOCK_FILE="${WORKSPACE}/memory/.task-timers.lock"
        # (
        #     flock -w 5 200 || exit 0
        #     TMP_FILE=$(mktemp)
        #     jq --arg tid "$TASK_ID" --argjson rc "$NEW_RETRY" \
        #         '.tasks[$tid].retry_count = $rc' "$TIMERS_FILE" > "$TMP_FILE" 2>/dev/null && \
        #         mv "$TMP_FILE" "$TIMERS_FILE"
        # ) 200>"$LOCK_FILE" 2>/dev/null || true
        #
        # RETRY_SUFFIX=$(echo "$TASK_ID" | grep -o '+[0-9]*$' || echo "")
        # BASE_FOR_RETRY=$(echo "$TASK_ID" | sed 's/+[0-9]*$//')
        # if [[ -n "$RETRY_SUFFIX" ]]; then
        #     CURRENT_RETRY=$(echo "$RETRY_SUFFIX" | tr -d '+')
        #     NEXT_RETRY=$((CURRENT_RETRY + 1))
        # else
        #     NEXT_RETRY=1
        # fi
        # NEW_TASK_ID="${BASE_FOR_RETRY}+${NEXT_RETRY}"
        # TEAM="$TEAM_ID"
        # if [[ -z "$TASK_FILE" || "$TASK_FILE" == "null" ]]; then
        #     TASK_FILE="${WORKSPACE}/memory/tasks/${TASK_ID}.md"
        # fi
        # log "${TASK_ID}: dispatch.py 재위임 → ${NEW_TASK_ID}"
        # unset CLAUDECODE
        # /usr/bin/python3 "${WORKSPACE}/dispatch.py" \
        #     --team "$TEAM" \
        #     --task-file "$TASK_FILE" \
        #     --task-id "$NEW_TASK_ID" \
        #     --level normal \
        #     --type coding \
        #     --override-routing \
        #     --skip-qc-gate \
        #     >> "$LOG_FILE" 2>&1 || true
        #
        # REDELEGATION_COUNT=$((REDELEGATION_COUNT + 1))
        # REDELEGATED_LIST+=("${TASK_ID}→${NEW_TASK_ID}(${TEAM_ID})")
        #
        # echo "$NOW" > "${HEARTBEAT_DIR}/${TASK_ID}.backoff"
        # echo "0" >> "${HEARTBEAT_DIR}/${TASK_ID}.backoff"
    else
        log "${TASK_ID}: task_file 없음 (${TASK_FILE}), 재위임 불가"
        STALLED_LIST+=("${TASK_ID}(${TEAM_ID},no-taskfile)")
    fi

done <<< "$RUNNING_TASKS"

# 집계 알림 전송
ALERT_PARTS=()
if [[ ${#REDELEGATED_LIST[@]} -gt 0 ]]; then
    ALERT_PARTS+=("🔄 재위임 ${#REDELEGATED_LIST[@]}건: $(IFS=', '; echo "${REDELEGATED_LIST[*]}")")
fi
if [[ ${#ESCALATED_LIST[@]} -gt 0 ]]; then
    ALERT_PARTS+=("🚨 에스컬레이션 ${#ESCALATED_LIST[@]}건: $(IFS=', '; echo "${ESCALATED_LIST[*]}")")
fi
if [[ ${#STALLED_LIST[@]} -gt 0 ]]; then
    ALERT_PARTS+=("⚠️ stalled ${#STALLED_LIST[@]}건: $(IFS=', '; echo "${STALLED_LIST[*]}")")
fi

if [[ ${#ALERT_PARTS[@]} -gt 0 ]]; then
    ALERT_MSG=$(printf '[Watchdog] %s\n' "${ALERT_PARTS[@]}")
    curl -s -X POST "https://api.telegram.org/bot${ANU_BOT_TOKEN}/sendMessage" \
        --data-urlencode "chat_id=6937032012" \
        --data-urlencode "text=${ALERT_MSG}" \
        > /dev/null 2>&1
fi

log "워치독 사이클 완료"
