#!/bin/bash
# Check for new PR reviews and comments
# Returns exit code 0 if new reviews exist, 1 if no new reviews

set -euo pipefail

# ============================================================================
# Configuration
# ============================================================================

# Bot reviewer usernames (GitHub bot accounts)
readonly COPILOT_BOT_LOGIN="copilot-pull-request-reviewer[bot]"
readonly CLAUDE_BOT_LOGIN="claude[bot]"

# Emoji toggle (set NO_EMOJI=1 to disable emoji output)
if [ "${NO_EMOJI:-0}" = "1" ]; then
    readonly SUCCESS="[OK]"
    readonly FAILURE="[FAIL]"
else
    readonly SUCCESS="✅"
    readonly FAILURE="❌"
fi

# ============================================================================
# Paths
# ============================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
STATE_DIR="$PROJECT_DIR/.claude/state"

PR_NUMBER="${1:-}"

if [ -z "$PR_NUMBER" ]; then
    echo "Usage: $0 <pr-number>" >&2
    exit 2
fi

STATE_FILE="${STATE_DIR}/pr_${PR_NUMBER}_monitor.json"

if [ ! -f "$STATE_FILE" ]; then
    echo "Error: No monitoring state found for PR #$PR_NUMBER" >&2
    echo "  State file not found: $STATE_FILE" >&2
    exit 2
fi

# Get repository info from state file
REPO_OWNER=$(jq -r '.repo_owner // empty' "$STATE_FILE" 2>/dev/null || echo "")
REPO_NAME=$(jq -r '.repo_name // empty' "$STATE_FILE" 2>/dev/null || echo "")

# Fallback to gh repo view if not in state
if [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ]; then
    REPO_OWNER=$(gh repo view --json owner -q '.owner.login' 2>/dev/null || echo "")
    REPO_NAME=$(gh repo view --json name -q '.name' 2>/dev/null || echo "")
fi

if [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ]; then
    echo "Error: Could not determine repository" >&2
    exit 2
fi

# Get last check timestamp (default to 1970 if never checked)
LAST_CHECK=$(jq -r '.last_review_check // "1970-01-01T00:00:00Z"' "$STATE_FILE")

# Check for new reviews
REVIEWS_COUNT=$(gh api "repos/$REPO_OWNER/$REPO_NAME/pulls/$PR_NUMBER/reviews" \
    --jq "[.[] | select(.submitted_at > \"$LAST_CHECK\")] | length" 2>/dev/null || echo "0")

# Check for new review comments (line-specific)
REVIEW_COMMENTS_COUNT=$(gh api "repos/$REPO_OWNER/$REPO_NAME/pulls/$PR_NUMBER/comments" \
    --jq "[.[] | select(.created_at > \"$LAST_CHECK\")] | length" 2>/dev/null || echo "0")

# Check for new issue comments (general PR comments)
ISSUE_COMMENTS_COUNT=$(gh api "repos/$REPO_OWNER/$REPO_NAME/issues/$PR_NUMBER/comments" \
    --jq "[.[] | select(.created_at > \"$LAST_CHECK\")] | length" 2>/dev/null || echo "0")

# ============================================================================
# CI/CD Status Check (CRITICAL - MUST DETECT FAILING CHECKS)
# ============================================================================

echo "Checking CI status for PR #$PR_NUMBER..." >&2

# Get PR head SHA
HEAD_SHA=$(gh api "repos/$REPO_OWNER/$REPO_NAME/pulls/$PR_NUMBER" --jq '.head.sha' 2>/dev/null || echo "")

if [ -z "$HEAD_SHA" ]; then
    echo "Warning: Could not get HEAD SHA for PR #$PR_NUMBER" >&2
    CI_FAILING=0
    CI_WARNINGS=0
elif [[ ! "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
    echo "Warning: Invalid HEAD SHA format: $HEAD_SHA" >&2
    CI_FAILING=0
    CI_WARNINGS=0
else
    # Get all check runs for this commit
    # CRITICAL: Fail-safe - if API fails, we MUST NOT assume "all clear"
    CI_API_FAILED=0
    if ! CHECK_RUNS=$(gh api "repos/$REPO_OWNER/$REPO_NAME/commits/$HEAD_SHA/check-runs" --jq '.check_runs' 2>&1); then
        echo "  $FAILURE CRITICAL: Failed to fetch check-runs from GitHub API" >&2
        echo "    API Error: $CHECK_RUNS" >&2
        echo "    FAIL-SAFE: Assuming CI checks are failing (cannot verify)" >&2
        CI_API_FAILED=1
        CHECK_RUNS="[]"
    fi

    # Validate JSON structure (FAIL-SAFE: treat invalid response as API failure)
    if ! echo "$CHECK_RUNS" | jq -e '. | type == "array"' >/dev/null 2>&1; then
        echo "  $FAILURE CRITICAL: Invalid check-runs response from API" >&2
        echo "    Response: $CHECK_RUNS" >&2
        echo "    FAIL-SAFE: Assuming CI checks are failing (cannot parse)" >&2
        CI_API_FAILED=1
        CHECK_RUNS="[]"
    fi

    # If API failed, treat as failing CI (fail-safe behavior)
    # We cannot verify CI status, so we must trigger iteration
    if [ "$CI_API_FAILED" -eq 1 ]; then
        CI_FAILING=1  # Cannot verify = assume failing (fail-safe)
        CI_PENDING=0
        TOTAL_CHECKS=0
    else
        # Count failing checks
        if ! CI_FAILING=$(echo "$CHECK_RUNS" | jq '[.[] | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out")] | length' 2>&1); then
            echo "Warning: Failed to parse check-runs with jq: $CI_FAILING" >&2
            CI_FAILING=0
        fi

        # Count pending/in-progress checks (CRITICAL - prevents premature "ready to merge")
        if ! CI_PENDING=$(echo "$CHECK_RUNS" | jq '[.[] | select(.status == "queued" or .status == "in_progress" or .status == "pending" or .status == "waiting")] | length' 2>&1); then
            echo "Warning: Failed to parse pending checks: $CI_PENDING" >&2
            CI_PENDING=0
        fi

        # Count total checks
        TOTAL_CHECKS=$(echo "$CHECK_RUNS" | jq 'length' || echo "0")
    fi

    # Count checks with warnings (even if they passed)
    # TODO (FUTURE WORK): Implement warning detection by parsing check-run logs
    # This requires fetching job logs via Actions API and scanning for patterns:
    #   - pytest: "PytestWarning:", "DeprecationWarning:"
    #   - ruff: non-blocking warnings
    #   - mypy: "note:" messages
    # Implementation plan:
    #   1. Fetch job logs via gh api repos/{owner}/{repo}/actions/jobs/{job_id}/logs
    #   2. Parse logs with grep/awk for warning patterns
    #   3. Count distinct warnings and update CI_WARNINGS
    # Status: Deferred to Phase 6 (future enhancement) - not blocking for MVP
    # For now, CI_WARNINGS is always 0
    CI_WARNINGS=0

    # Report CI status
    if [ "$CI_API_FAILED" -eq 1 ]; then
        # API failed, cannot verify status
        echo "  $FAILURE CANNOT VERIFY CI STATUS - API Error" >&2
        echo "    This PR should NOT be declared ready until CI status can be verified" >&2
    elif [ "$CI_FAILING" -gt 0 ]; then
        echo "  $FAILURE Found $CI_FAILING failing CI check(s)" >&2
        # Log details of failing checks
        echo "$CHECK_RUNS" | jq -r '.[] | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out") | "    - \(.name): \(.conclusion)"' >&2
    elif [ "$CI_PENDING" -gt 0 ]; then
        echo "  ⏳ $CI_PENDING CI check(s) still pending/in-progress" >&2
        # Log details of pending checks
        echo "$CHECK_RUNS" | jq -r '.[] | select(.status == "queued" or .status == "in_progress" or .status == "pending" or .status == "waiting") | "    - \(.name): \(.status)"' >&2
    elif [ "$TOTAL_CHECKS" -eq 0 ]; then
        echo "  ⚠️  Warning: No CI checks configured for this PR" >&2
    else
        echo "  $SUCCESS All CI checks passing ($TOTAL_CHECKS checks)" >&2
    fi

    # Check for warnings in logs (even for passing checks)
    # Look for common warning patterns: pytest warnings, ruff warnings, mypy warnings
    # This is complex and may require separate enhancement
fi

# ============================================================================
# Bot-Specific Review Detection
# ============================================================================

# Count bot comments created or updated after a timestamp
# Usage: count_bot_comments <bot_login> <all_comments_json> <timestamp>
# Note: Checks both created_at and updated_at to catch edited comments
count_bot_comments() {
    local bot_login="$1"
    local comments_json="$2"
    local since_timestamp="$3"
    local count

    # Use jq --arg for safe variable passing (prevents injection)
    # Check both created_at AND updated_at to catch edited comments
    count=$(echo "$comments_json" | jq --arg login "$bot_login" --arg ts "$since_timestamp" \
        '[.[] | select(.user.login == $login) | select(.created_at > $ts or .updated_at > $ts)] | length' || echo "0")

    # Validate numeric output with regex instead of silent character stripping
    if [[ ! "$count" =~ ^[0-9]+$ ]]; then
        count=0
    fi

    echo "$count"
}

# Fetch all bot comments once with timestamp filtering
# Note: Using same timestamp filter as human reviews to prevent infinite iterations
if ! ALL_COMMENTS=$(gh api "repos/$REPO_OWNER/$REPO_NAME/issues/$PR_NUMBER/comments" 2>&1); then
    echo "Warning: Failed to fetch PR comments: $ALL_COMMENTS" >&2
    ALL_COMMENTS='[]'
fi

# Count bot comments created after LAST_CHECK
COPILOT_BOT_COMMENTS=$(count_bot_comments "$COPILOT_BOT_LOGIN" "$ALL_COMMENTS" "$LAST_CHECK")
CLAUDE_BOT_COMMENTS=$(count_bot_comments "$CLAUDE_BOT_LOGIN" "$ALL_COMMENTS" "$LAST_CHECK")

# Total feedback indicators
TOTAL_NEW=$((REVIEWS_COUNT + REVIEW_COMMENTS_COUNT + ISSUE_COMMENTS_COUNT))

# ============================================================================
# Update State File
# ============================================================================

update_state_file() {
    local now="$1"
    local new="$2"
    local ci_failing="$3"
    local ci_warnings="$4"
    local copilot="$5"
    local claude="$6"
    local tmp_file
    tmp_file=$(mktemp)

    jq \
        --arg now "$now" \
        --argjson new "$new" \
        --argjson ci_failing "$ci_failing" \
        --argjson ci_warnings "$ci_warnings" \
        --argjson copilot "$copilot" \
        --argjson claude "$claude" \
        '.last_review_check = $now |
         .new_items_found = $new |
         .ci_failing = $ci_failing |
         .ci_warnings = $ci_warnings |
         .copilot_bot_comments = $copilot |
         .claude_bot_comments = $claude' \
        "$STATE_FILE" > "$tmp_file" || { rm -f "$tmp_file"; return 1; }

    mv "$tmp_file" "$STATE_FILE" || { rm -f "$tmp_file"; return 1; }
}

NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Use file locking to prevent race conditions from concurrent iterations
# Cross-platform implementation using mkdir (atomic on all platforms)
LOCK_FILE="${STATE_FILE}.lock"

# Cleanup lock on exit
# shellcheck disable=SC2317  # Function is invoked by trap, not directly called
cleanup_lock() {
    rmdir "$LOCK_FILE" 2>/dev/null || true
}
trap cleanup_lock EXIT

# Acquire lock with timeout (max 10 seconds)
LOCK_SLEEP=0.1
LOCK_MAX_ATTEMPTS=100  # 100 iterations * 0.1s = 10 seconds
LOCK_ATTEMPTS=0

while ! mkdir "$LOCK_FILE" 2>/dev/null; do
    LOCK_ATTEMPTS=$((LOCK_ATTEMPTS + 1))
    if [ "$LOCK_ATTEMPTS" -ge "$LOCK_MAX_ATTEMPTS" ]; then
        echo "Error: Could not acquire lock after 10 seconds" >&2
        echo "  Lock file: $LOCK_FILE" >&2
        echo "  This may indicate a stale lock or concurrent process" >&2
        echo "" >&2
        echo "  To recover from stale lock:" >&2
        echo "    1. Verify no other instances are running: ps aux | grep check-pr-reviews" >&2
        echo "    2. If no processes found: rmdir $LOCK_FILE" >&2
        echo "    3. Then retry the operation" >&2
        exit 3
    fi
    sleep "$LOCK_SLEEP"
done

# Update state file and exit on failure
if ! update_state_file "$NOW" "$TOTAL_NEW" "$CI_FAILING" "$CI_WARNINGS" "$COPILOT_BOT_COMMENTS" "$CLAUDE_BOT_COMMENTS"; then
    echo "Error: Failed to update state file" >&2
    exit 3
fi

# ============================================================================
# Determine if iteration needed
# ============================================================================

SHOULD_ITERATE=0
REASONS=()

if [ "$CI_FAILING" -gt 0 ]; then
    SHOULD_ITERATE=1
    REASONS+=("$CI_FAILING failing CI check(s)")
fi

# CRITICAL: Pending checks block merge - must wait for completion
if [ "${CI_PENDING:-0}" -gt 0 ]; then
    SHOULD_ITERATE=1
    REASONS+=("$CI_PENDING pending/in-progress CI check(s) - waiting for completion")
fi

if [ "$CI_WARNINGS" -gt 0 ]; then
    SHOULD_ITERATE=1
    REASONS+=("$CI_WARNINGS CI warning(s)")
fi

if [ "$TOTAL_NEW" -gt 0 ]; then
    SHOULD_ITERATE=1
    REASONS+=("$TOTAL_NEW new review item(s)")
fi

if [ "$COPILOT_BOT_COMMENTS" -gt 0 ]; then
    SHOULD_ITERATE=1
    REASONS+=("$COPILOT_BOT_COMMENTS copilot-pull-request-reviewer[bot] comment(s)")
fi

if [ "$CLAUDE_BOT_COMMENTS" -gt 0 ]; then
    SHOULD_ITERATE=1
    REASONS+=("$CLAUDE_BOT_COMMENTS claude[bot] comment(s)")
fi

# Output summary
if [ "$SHOULD_ITERATE" -eq 1 ]; then
    echo "$SUCCESS Iteration needed for PR #$PR_NUMBER"
    echo ""
    echo "Reasons:"
    for reason in "${REASONS[@]}"; do
        echo "  - $reason"
    done
    echo ""
    echo "Details:"
    echo "  - New reviews: $REVIEWS_COUNT"
    echo "  - New review comments: $REVIEW_COMMENTS_COUNT"
    echo "  - New issue comments: $ISSUE_COMMENTS_COUNT"
    echo "  - Copilot bot comments: $COPILOT_BOT_COMMENTS"
    echo "  - Claude bot comments: $CLAUDE_BOT_COMMENTS"
    echo "  - Failing CI checks: $CI_FAILING"
    echo "  - Pending CI checks: ${CI_PENDING:-0}"
    echo "  - CI warnings: $CI_WARNINGS"
    exit 0
else
    echo "No iteration needed for PR #$PR_NUMBER"
    echo "  - No new reviews"
    echo "  - No bot comments"
    echo "  - All CI checks complete and passing"
    echo "  - No pending or failing checks"
    echo "  - No warnings detected"
    exit 1
fi
