#!/usr/bin/env bash
# agent-loop.sh — Universal agent dev loop with Genchi Genbutsu verification
#
# Usage: agent-loop.sh <agent-name> [num-workers]
#   agent-loop.sh claude 2
#   agent-loop.sh gemini 1
#
# Dispatches via agent-dispatch.sh, then verifies with genchi-genbutsu.sh.

set -uo pipefail

AGENT="${1:?Usage: agent-loop.sh <agent-name> [num-workers]}"
NUM_WORKERS="${2:-1}"

# Resolve agent tool and model from config or fallback
case "$AGENT" in
  claude) TOOL="claude"; MODEL="sonnet" ;;
  gemini) TOOL="gemini"; MODEL="gemini-2.5-pro-preview-05-06" ;;
  grok)   TOOL="opencode"; MODEL="grok-3-fast" ;;
  gemma4) TOOL="hermes"; MODEL="google/gemma-4-31b-it"; PROVIDER="openrouter" ;;
  *)      TOOL="$AGENT"; MODEL="" ;;
esac

# === CONFIG ===
GITEA_URL="${GITEA_URL:-https://forge.alexanderwhitestone.com}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
WORKTREE_BASE="$HOME/worktrees"
LOG_DIR="$HOME/.hermes/logs"
LOCK_DIR="$LOG_DIR/${AGENT}-locks"
SKIP_FILE="$LOG_DIR/${AGENT}-skip-list.json"
ACTIVE_FILE="$LOG_DIR/${AGENT}-active.json"
TIMEOUT=600
COOLDOWN=30

mkdir -p "$LOG_DIR" "$WORKTREE_BASE" "$LOCK_DIR"
[ -f "$SKIP_FILE" ] || echo '{}' > "$SKIP_FILE"
echo '{}' > "$ACTIVE_FILE"

# === SHARED FUNCTIONS ===
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${AGENT}: $*" >> "$LOG_DIR/${AGENT}-loop.log"
}

lock_issue() {
  local key="$1"
  mkdir "$LOCK_DIR/$key.lock" 2>/dev/null && echo $$ > "$LOCK_DIR/$key.lock/pid"
}

unlock_issue() {
  rm -rf "$LOCK_DIR/$1.lock" 2>/dev/null
}

mark_skip() {
  local issue_num="$1" reason="$2"
  python3 -c "
import json, time, fcntl
with open('${SKIP_FILE}', 'r+') as f:
    fcntl.flock(f, fcntl.LOCK_EX)
    try: skips = json.load(f)
    except: skips = {}
    failures = skips.get(str($issue_num), {}).get('failures', 0) + 1
    skip_hours = 6 if failures >= 3 else 1
    skips[str($issue_num)] = {'until': time.time() + (skip_hours * 3600), 'reason': '$reason', 'failures': failures}
    f.seek(0); f.truncate()
    json.dump(skips, f, indent=2)
" 2>/dev/null
}

get_next_issue() {
  python3 -c "
import json, sys, time, urllib.request, os
token = '${GITEA_TOKEN}'
base = '${GITEA_URL}'
repos = ['Timmy_Foundation/the-nexus', 'Timmy_Foundation/timmy-config', 'Timmy_Foundation/hermes-agent']
try:
    with open('${SKIP_FILE}') as f: skips = json.load(f)
except: skips = {}
try:
    with open('${ACTIVE_FILE}') as f: active = json.load(f); active_issues = {v['issue'] for v in active.values()}
except: active_issues = set()
all_issues = []
for repo in repos:
    url = f'{base}/api/v1/repos/{repo}/issues?state=open&type=issues&limit=50&sort=created'
    req = urllib.request.Request(url, headers={'Authorization': f'token {token}'})
    try:
        resp = urllib.request.urlopen(req, timeout=10)
        issues = json.loads(resp.read())
        for i in issues: i['_repo'] = repo
        all_issues.extend(issues)
    except: continue
for i in sorted(all_issues, key=lambda x: x['title'].lower()):
    assignees = [a['login'] for a in (i.get('assignees') or [])]
    if assignees and '${AGENT}' not in assignees: continue
    num_str = str(i['number'])
    if num_str in active_issues: continue
    if skips.get(num_str, {}).get('until', 0) > time.time(): continue
    lock = '${LOCK_DIR}/' + i['_repo'].replace('/', '-') + '-' + num_str + '.lock'
    if os.path.isdir(lock): continue
    owner, name = i['_repo'].split('/')
    print(json.dumps({'number': i['number'], 'title': i['title'], 'repo_owner': owner, 'repo_name': name, 'repo': i['_repo']}))
    sys.exit(0)
print('null')
" 2>/dev/null
}

# === WORKER FUNCTION ===
run_worker() {
  local worker_id="$1"
  log "WORKER-${worker_id}: Started"

  while true; do
    issue_json=$(get_next_issue)
    if [ "$issue_json" = "null" ] || [ -z "$issue_json" ]; then
      sleep 30
      continue
    fi

    issue_num=$(echo "$issue_json" | python3 -c "import sys,json; print(json.load(sys.stdin)['number'])")
    issue_title=$(echo "$issue_json" | python3 -c "import sys,json; print(json.load(sys.stdin)['title'])")
    repo_owner=$(echo "$issue_json" | python3 -c "import sys,json; print(json.load(sys.stdin)['repo_owner'])")
    repo_name=$(echo "$issue_json" | python3 -c "import sys,json; print(json.load(sys.stdin)['repo_name'])")
    issue_key="${repo_owner}-${repo_name}-${issue_num}"
    branch="${AGENT}/issue-${issue_num}"
    worktree="${WORKTREE_BASE}/${AGENT}-w${worker_id}-${issue_num}"

    if ! lock_issue "$issue_key"; then
      sleep 5
      continue
    fi

    log "WORKER-${worker_id}: === ISSUE #${issue_num}: ${issue_title} (${repo_owner}/${repo_name}) ==="

    # Clone / checkout
    rm -rf "$worktree" 2>/dev/null
    CLONE_URL="http://${AGENT}:${GITEA_TOKEN}@143.198.27.163:3000/${repo_owner}/${repo_name}.git"
    if git ls-remote --heads "$CLONE_URL" "$branch" 2>/dev/null | grep -q "$branch"; then
      git clone --depth=50 -b "$branch" "$CLONE_URL" "$worktree" >/dev/null 2>&1
    else
      git clone --depth=1 -b main "$CLONE_URL" "$worktree" >/dev/null 2>&1
      cd "$worktree" && git checkout -b "$branch" >/dev/null 2>&1
    fi
    cd "$worktree"

    # Generate prompt
    prompt=$(bash "$(dirname "$0")/agent-dispatch.sh" "$AGENT" "$issue_num" "${repo_owner}/${repo_name}")

    CYCLE_START=$(date +%s)
    set +e
    if [ "$TOOL" = "claude" ]; then
      env -u CLAUDECODE gtimeout "$TIMEOUT" claude \
        --print --model "$MODEL" --dangerously-skip-permissions \
        -p "$prompt" </dev/null >> "$LOG_DIR/${AGENT}-${issue_num}.log" 2>&1
    elif [ "$TOOL" = "gemini" ]; then
      gtimeout "$TIMEOUT" gemini -p "$prompt" --yolo \
        </dev/null >> "$LOG_DIR/${AGENT}-${issue_num}.log" 2>&1
    else
      gtimeout "$TIMEOUT" "$TOOL" "$prompt" \
        </dev/null >> "$LOG_DIR/${AGENT}-${issue_num}.log" 2>&1
    fi
    exit_code=$?
    set -e
    CYCLE_END=$(date +%s)
    CYCLE_DURATION=$((CYCLE_END - CYCLE_START))

    # --- Mid-session auto-commit: commit before timeout if work is dirty ---
    cd "$worktree" 2>/dev/null || true
    # Ensure auto-commit-guard is running
    if ! pgrep -f "auto-commit-guard.sh" >/dev/null 2>&1; then
      log "Starting auto-commit-guard daemon"
      nohup bash "$(dirname "$0")/auto-commit-guard.sh" 120 "$WORKTREE_BASE"         >> "$LOG_DIR/auto-commit-guard.log" 2>&1 &
    fi

    # Salvage
    cd "$worktree" 2>/dev/null || true
    DIRTY=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
    if [ "${DIRTY:-0}" -gt 0 ]; then
      git add -A 2>/dev/null
      git commit -m "WIP: ${AGENT} progress on #${issue_num}

Automated salvage commit — agent session ended (exit $exit_code)." 2>/dev/null || true
    fi

    UNPUSHED=$(git log --oneline "origin/main..HEAD" 2>/dev/null | wc -l | tr -d ' ')
    if [ "${UNPUSHED:-0}" -gt 0 ]; then
      git push -u origin "$branch" 2>/dev/null && \
        log "WORKER-${worker_id}: Pushed $UNPUSHED commit(s) on $branch" || \
        log "WORKER-${worker_id}: Push failed for $branch"
    fi

    # Create PR if needed
    pr_num=$(curl -sf "${GITEA_URL}/api/v1/repos/${repo_owner}/${repo_name}/pulls?state=open&head=${repo_owner}:${branch}&limit=1" \
      -H "Authorization: token ${GITEA_TOKEN}" | python3 -c "
import sys,json
prs = json.load(sys.stdin)
print(prs[0]['number'] if prs else '')
" 2>/dev/null)

    if [ -z "$pr_num" ] && [ "${UNPUSHED:-0}" -gt 0 ]; then
      pr_num=$(curl -sf -X POST "${GITEA_URL}/api/v1/repos/${repo_owner}/${repo_name}/pulls" \
        -H "Authorization: token ${GITEA_TOKEN}" \
        -H "Content-Type: application/json" \
        -d "$(python3 -c "
import json
print(json.dumps({
    'title': '${AGENT}: Issue #${issue_num}',
    'head': '${branch}',
    'base': 'main',
    'body': 'Automated PR for issue #${issue_num}.\nExit code: ${exit_code}'
}))
")" | python3 -c "import sys,json; print(json.load(sys.stdin).get('number',''))" 2>/dev/null)
      [ -n "$pr_num" ] && log "WORKER-${worker_id}: Created PR #${pr_num} for issue #${issue_num}"
    fi

    # ── Genchi Genbutsu: verify world state before declaring success ──
    VERIFIED="false"
    if [ "$exit_code" -eq 0 ]; then
      log "WORKER-${worker_id}: SUCCESS #${issue_num} — running genchi-genbutsu"
      SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
      if verify_result=$("$SCRIPT_DIR/genchi-genbutsu.sh" "$repo_owner" "$repo_name" "$issue_num" "$branch" "$AGENT" 2>/dev/null); then
        VERIFIED="true"
        log "WORKER-${worker_id}: VERIFIED #${issue_num}"
        if [ -n "$pr_num" ]; then
          curl -sf -X POST "${GITEA_URL}/api/v1/repos/${repo_owner}/${repo_name}/pulls/${pr_num}/merge" \
            -H "Authorization: token ${GITEA_TOKEN}" \
            -H "Content-Type: application/json" \
            -d '{"Do": "squash"}' >/dev/null 2>&1 || true
          curl -sf -X PATCH "${GITEA_URL}/api/v1/repos/${repo_owner}/${repo_name}/issues/${issue_num}" \
            -H "Authorization: token ${GITEA_TOKEN}" \
            -H "Content-Type: application/json" \
            -d '{"state": "closed"}' >/dev/null 2>&1 || true
          log "WORKER-${worker_id}: PR #${pr_num} merged, issue #${issue_num} closed"
        fi
        consecutive_failures=0
      else
        verify_details=$(echo "$verify_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('details','unknown'))" 2>/dev/null || echo "unverified")
        log "WORKER-${worker_id}: UNVERIFIED #${issue_num} — $verify_details"
        mark_skip "$issue_num" "unverified" 1
        consecutive_failures=$((consecutive_failures + 1))
      fi
    elif [ "$exit_code" -eq 124 ]; then
      log "WORKER-${worker_id}: TIMEOUT #${issue_num} (work saved in PR)"
      consecutive_failures=$((consecutive_failures + 1))
    else
      log "WORKER-${worker_id}: FAILED #${issue_num} exit ${exit_code} (work saved in PR)"
      consecutive_failures=$((consecutive_failures + 1))
    fi

    # ── METRICS ──
    python3 -c "
import json, datetime
print(json.dumps({
    'ts': datetime.datetime.utcnow().isoformat() + 'Z',
    'agent': '${AGENT}',
    'worker': $worker_id,
    'issue': $issue_num,
    'repo': '${repo_owner}/${repo_name}',
    'outcome': 'success' if $exit_code == 0 else 'timeout' if $exit_code == 124 else 'failed',
    'exit_code': $exit_code,
    'duration_s': $CYCLE_DURATION,
    'pr': '${pr_num:-}',
    'verified': ${VERIFIED:-false}
}))
" >> "$LOG_DIR/${AGENT}-metrics.jsonl" 2>/dev/null

    rm -rf "$worktree" 2>/dev/null
    unlock_issue "$issue_key"
    sleep "$COOLDOWN"
  done
}

# === MAIN ===
log "=== Agent Loop Started — ${AGENT} with ${NUM_WORKERS} worker(s) ==="

rm -rf "$LOCK_DIR"/*.lock 2>/dev/null

for i in $(seq 1 "$NUM_WORKERS"); do
  run_worker "$i" &
  log "Launched worker $i (PID $!)"
  sleep 3
done

wait
