#!/bin/bash
# pr-ci-wait.sh - Wait for PR CI status to reach terminal state (GitHub/Gitea)
# Usage: pr-ci-wait.sh -n <pr_number> [-r owner/repo] [-t timeout_sec] [-i interval_sec]

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/detect-platform.sh"

PR_NUMBER=""
TIMEOUT_SEC=1800
INTERVAL_SEC=15
REPO_OVERRIDE=""
HOST_OVERRIDE=""

usage() {
    cat <<EOF
Usage: $(basename "$0") -n <pr_number> [-t timeout_sec] [-i interval_sec]

Options:
  -n, --number NUMBER      PR number (required)
  -r, --repo OWNER/REPO    Repository slug (default: infer from git origin)
      --host HOST          Gitea host for --repo API calls (or set GITEA_HOST/GITEA_URL)
  -t, --timeout SECONDS    Max wait time in seconds (default: 1800)
  -i, --interval SECONDS   Poll interval in seconds (default: 15)
  -h, --help               Show this help

Examples:
  $(basename "$0") -n 643
  $(basename "$0") -n 643 --repo ddk/ai-bma
  $(basename "$0") -n 643 -t 900 -i 10
EOF
}

# get_remote_host and get_gitea_token are provided by detect-platform.sh

extract_state_from_status_json() {
    # Capture piped JSON BEFORE invoking `python3 - <<PY`. The heredoc binds
    # stdin to the Python program text — so json.load(sys.stdin) inside would
    # try to re-read stdin after `-` already consumed it for the program,
    # yielding EOF and returning "unknown" every time. Pass payload via env.
    local payload
    payload=$(cat)
    PR_CI_STATUS_JSON="$payload" python3 - <<'PY'
import json
import os
import sys

try:
    payload = json.loads(os.environ.get("PR_CI_STATUS_JSON", ""))
except Exception:
    print("unknown")
    raise SystemExit(0)

state = (payload.get("state") or "").lower()
if state in {"success", "pending", "failure", "error"}:
    print(state)
    raise SystemExit(0)

statuses = payload.get("statuses") or []
values = []
for item in statuses:
    if not isinstance(item, dict):
        continue
    value = (item.get("status") or item.get("state") or "").lower()
    if value:
        values.append(value)

if any(v in {"failure", "error"} for v in values):
    print("failure")
elif values and all(v == "success" for v in values):
    print("success")
elif any(v in {"pending", "running", "queued", "waiting"} for v in values):
    print("pending")
elif not values and not state:
    # No pipeline/status of any kind reported for this commit. Distinct from
    # "unknown" (an ambiguous/unrecognized status that should keep polling):
    # this signals a repo/commit that simply has no CI configured.
    print("no-status")
else:
    print("unknown")
PY
}

print_status_summary() {
    # Same stdin-collision fix as extract_state_from_status_json above.
    local payload
    payload=$(cat)
    PR_CI_STATUS_JSON="$payload" python3 - <<'PY'
import json
import os
import sys

try:
    payload = json.loads(os.environ.get("PR_CI_STATUS_JSON", ""))
except Exception:
    print("[pr-ci-wait] status payload unavailable")
    raise SystemExit(0)

statuses = payload.get("statuses") or []
if not statuses:
    print("[pr-ci-wait] no status contexts reported yet")
    raise SystemExit(0)

for item in statuses:
    if not isinstance(item, dict):
        continue
    name = item.get("context") or item.get("name") or "unknown-context"
    state = item.get("status") or item.get("state") or "unknown-state"
    target = item.get("target_url") or item.get("url") or ""
    if target:
        print(f"[pr-ci-wait] {name}: {state} ({target})")
    else:
        print(f"[pr-ci-wait] {name}: {state}")
PY
}

github_get_pr_head_sha() {
    gh pr view "$PR_NUMBER" --repo "$OWNER/$REPO" --json headRefOid --jq '.headRefOid'
}

github_get_commit_status_json() {
    local owner="$1"
    local repo="$2"
    local sha="$3"
    gh api "repos/${owner}/${repo}/commits/${sha}/status"
}

gitea_get_pr_head_sha() {
    local host="$1"
    local repo="$2"
    local token="$3"
    local url="https://${host}/api/v1/repos/${repo}/pulls/${PR_NUMBER}"
    curl -fsSL -H "User-Agent: curl/8" -H "Authorization: token ${token}" "$url" | python3 -c '
import json, sys
data = json.load(sys.stdin)
print((data.get("head") or {}).get("sha", ""))
'
}

gitea_get_commit_status_json() {
    local host="$1"
    local repo="$2"
    local token="$3"
    local sha="$4"
    local url="https://${host}/api/v1/repos/${repo}/commits/${sha}/status"
    curl -fsSL -H "User-Agent: curl/8" -H "Authorization: token ${token}" "$url"
}

gitea_get_default_branch() {
    local host="$1"
    local repo="$2"
    local token="$3"
    local url="https://${host}/api/v1/repos/${repo}"
    curl -fsSL -H "User-Agent: curl/8" -H "Authorization: token ${token}" "$url" | python3 -c '
import json, sys
print((json.load(sys.stdin) or {}).get("default_branch", ""))
'
}

github_get_default_branch() {
    gh api "repos/${OWNER}/${REPO}" --jq '.default_branch'
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        -n|--number)
            PR_NUMBER="$2"
            shift 2
            ;;
        -r|--repo)
            REPO_OVERRIDE="$2"
            shift 2
            ;;
        --host)
            HOST_OVERRIDE="$2"
            shift 2
            ;;
        -t|--timeout)
            TIMEOUT_SEC="$2"
            shift 2
            ;;
        -i|--interval)
            INTERVAL_SEC="$2"
            shift 2
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            echo "Unknown option: $1" >&2
            usage >&2
            exit 1
            ;;
    esac
done

if [[ -z "$PR_NUMBER" ]]; then
    echo "Error: PR number is required (-n)." >&2
    usage >&2
    exit 1
fi

if ! [[ "$TIMEOUT_SEC" =~ ^[0-9]+$ ]] || ! [[ "$INTERVAL_SEC" =~ ^[0-9]+$ ]]; then
    echo "Error: timeout and interval must be integer seconds." >&2
    exit 1
fi

if [[ -n "$REPO_OVERRIDE" ]]; then
    REPO_INFO="$REPO_OVERRIDE"
    PLATFORM=$(detect_platform 2>/dev/null || echo gitea)
else
    detect_platform > /dev/null
    REPO_INFO=$(get_repo_info)
fi

if [[ -z "$REPO_INFO" || "$REPO_INFO" == error:* || "$REPO_INFO" != */* ]]; then
    echo "Error: Could not determine repository from git origin. Run from a repo or pass --repo owner/repo." >&2
    exit 1
fi

OWNER=${REPO_INFO%%/*}
REPO=${REPO_INFO##*/}
START_TS=$(date +%s)
DEADLINE_TS=$((START_TS + TIMEOUT_SEC))

if [[ "$PLATFORM" == "github" ]]; then
    if ! command -v gh >/dev/null 2>&1; then
        echo "Error: gh CLI is required for GitHub CI status polling." >&2
        exit 1
    fi
    HEAD_SHA=$(github_get_pr_head_sha)
    if [[ -z "$HEAD_SHA" ]]; then
        echo "Error: Could not resolve head SHA for PR #$PR_NUMBER." >&2
        exit 1
    fi
    echo "[pr-ci-wait] Platform=github PR=#${PR_NUMBER} head_sha=${HEAD_SHA}"
elif [[ "$PLATFORM" == "gitea" ]]; then
    if [[ -n "$HOST_OVERRIDE" ]]; then
        HOST="$HOST_OVERRIDE"
    elif [[ -n "$REPO_OVERRIDE" ]]; then
        HOST=$(get_gitea_api_host_for_repo_override) || {
            echo "Error: Gitea host is required with --repo. Pass --host or set GITEA_HOST/GITEA_URL." >&2
            exit 1
        }
    else
        HOST=$(get_remote_host) || {
            echo "Error: Could not determine Gitea host from git origin." >&2
            exit 1
        }
    fi
    TOKEN=$(get_gitea_token "$HOST") || {
        echo "Error: Gitea token not found. Set GITEA_TOKEN or configure ~/.git-credentials." >&2
        exit 1
    }
    HEAD_SHA=$(gitea_get_pr_head_sha "$HOST" "$OWNER/$REPO" "$TOKEN")
    if [[ -z "$HEAD_SHA" ]]; then
        echo "Error: Could not resolve head SHA for PR #$PR_NUMBER." >&2
        exit 1
    fi
    echo "[pr-ci-wait] Platform=gitea host=${HOST} repo=${OWNER}/${REPO} PR=#${PR_NUMBER} head_sha=${HEAD_SHA}"
else
    echo "Error: Unsupported platform '${PLATFORM}'." >&2
    exit 1
fi

# No-CI determination is TWO-TIER (primary: CI history; secondary: empty-poll streak).
#
# PRIMARY — "does this repo run CI at all?" Probed once, up front, from the DEFAULT
# BRANCH's commit status. A repo whose default branch carries CI statuses
# demonstrably runs CI, so an EMPTY status on the PR head means the pipeline simply
# has not registered YET (webhook/queue lag) — NOT that the repo is CI-less. In that
# case we must NEVER fast-green; we keep polling until the pipeline registers or the
# timeout fires (both safe). This closes the webhook-lag false-green: a slow-to-
# register pipeline feeding a merge gate can no longer be mistaken for "no CI".
#
# SECONDARY — the empty-poll streak below applies ONLY to genuinely CI-less repos
# (default branch also has no CI history, e.g. device-imaging class), where burning
# the full timeout would be pure waste. There, NO_CI_MAX empty polls => fast-exit 0.
#
# Probe failure is treated conservatively as REPO_HAS_CI=1 (assume CI present): we
# would rather wait-then-timeout than risk a false-green, per the merge-gate priority.
REPO_HAS_CI=1
detect_repo_ci() {
    local def_branch def_status
    # Every early exit returns 0: a probe miss must leave the conservative
    # REPO_HAS_CI=1 default in place, never abort the caller under `set -e`.
    if [[ "$PLATFORM" == "github" ]]; then
        def_branch=$(github_get_default_branch 2>/dev/null) || {
            echo "[pr-ci-wait] WARN: default-branch probe failed; assuming CI-enabled (will not fast-green on empty status)."; return 0; }
        [[ -n "$def_branch" ]] || return 0
        def_status=$(github_get_commit_status_json "$OWNER" "$REPO" "$def_branch" 2>/dev/null | extract_state_from_status_json) || return 0
    else
        def_branch=$(gitea_get_default_branch "$HOST" "$OWNER/$REPO" "$TOKEN" 2>/dev/null) || {
            echo "[pr-ci-wait] WARN: default-branch probe failed; assuming CI-enabled (will not fast-green on empty status)."; return 0; }
        [[ -n "$def_branch" ]] || return 0
        def_status=$(gitea_get_commit_status_json "$HOST" "$OWNER/$REPO" "$TOKEN" "$def_branch" 2>/dev/null | extract_state_from_status_json) || return 0
    fi
    if [[ "$def_status" == "no-status" || -z "$def_status" ]]; then
        REPO_HAS_CI=0
        echo "[pr-ci-wait] default branch '${def_branch}' has no CI status history — treating repo as CI-less (empty-poll fast-exit enabled)."
    else
        REPO_HAS_CI=1
        echo "[pr-ci-wait] default branch '${def_branch}' has CI history (state=${def_status}) — repo runs CI; empty status on PR head => awaiting registration, will not fast-green."
    fi
}
detect_repo_ci || true

NO_CI_STREAK=0
NO_CI_MAX=3

while true; do
    NOW_TS=$(date +%s)
    if (( NOW_TS > DEADLINE_TS )); then
        echo "Error: Timed out waiting for CI status on PR #$PR_NUMBER after ${TIMEOUT_SEC}s." >&2
        exit 124
    fi

    if [[ "$PLATFORM" == "github" ]]; then
        STATUS_JSON=$(github_get_commit_status_json "$OWNER" "$REPO" "$HEAD_SHA")
    else
        STATUS_JSON=$(gitea_get_commit_status_json "$HOST" "$OWNER/$REPO" "$TOKEN" "$HEAD_SHA")
    fi

    STATE=$(printf '%s' "$STATUS_JSON" | extract_state_from_status_json)
    echo "[pr-ci-wait] state=${STATE} pr=#${PR_NUMBER} sha=${HEAD_SHA}"

    case "$STATE" in
        success)
            printf '%s' "$STATUS_JSON" | print_status_summary
            echo "[pr-ci-wait] CI is green for PR #$PR_NUMBER."
            exit 0
            ;;
        failure|error)
            printf '%s' "$STATUS_JSON" | print_status_summary
            echo "Error: CI reported ${STATE} for PR #$PR_NUMBER." >&2
            exit 1
            ;;
        no-status)
            if [[ "$REPO_HAS_CI" == "1" ]]; then
                # PRIMARY tier: repo demonstrably runs CI but this commit's pipeline
                # has not registered yet (webhook/queue lag). Do NOT fast-green — keep
                # polling until it registers or the timeout fires. Reset the streak so
                # a later genuine CI-less misread can't accumulate across this state.
                NO_CI_STREAK=0
                echo "[pr-ci-wait] empty status on PR head but repo runs CI — awaiting pipeline registration (webhook lag), not fast-greening."
            else
                # SECONDARY tier: genuinely CI-less repo (default branch has no CI
                # history either). Empty polls => fast-exit green after NO_CI_MAX.
                NO_CI_STREAK=$((NO_CI_STREAK + 1))
                if (( NO_CI_STREAK >= NO_CI_MAX )); then
                    echo "[INFO] no CI configured for this repo/commit (PR #$PR_NUMBER, ${NO_CI_STREAK} consecutive empty polls, default branch also CI-less); treating as green."
                    exit 0
                fi
            fi
            sleep "$INTERVAL_SEC"
            ;;
        pending|unknown)
            # A pipeline exists but hasn't reached a terminal state (or is
            # transiently ambiguous) — keep waiting, and reset the no-CI streak
            # since this commit is not in the "no CI at all" condition.
            NO_CI_STREAK=0
            sleep "$INTERVAL_SEC"
            ;;
        *)
            echo "[pr-ci-wait] Unrecognized state '${STATE}', continuing to poll..."
            NO_CI_STREAK=0
            sleep "$INTERVAL_SEC"
            ;;
    esac
done
