#!/bin/bash
# Git Pre-push Hook for OrchestKit Plugin
# Mirrors CI version-check.yml - fail fast locally before pushing
# Version: 1.0.0

set -uo pipefail

PROJECT_ROOT="$(git rev-parse --show-toplevel)"
cd "$PROJECT_ROOT"

BRANCH=$(git rev-parse --abbrev-ref HEAD)
REMOTE="$1"
REMOTE_URL="$2"

echo "Running pre-push validations for branch: $BRANCH"

# ===== Skip for branches handled by release-please =====
# release-please is "release-type: simple" and computes next version from
# conventional-commit types since last tag — manual bumps on these branches
# create "ghost versions" when the two disagree (see #1457). Skip enforcement
# on all conventional-commit prefixes; release-please owns the version.
# Bare-named branches (e.g. hotfixes) still get enforced.
if [[ "$BRANCH" =~ ^(docs|chore|ci|style|test|feat|fix|perf|refactor)/ ]]; then
  echo "  ✓ Skipping version check for $BRANCH (release-please handles versioning)"
  exit 0
fi

# ===== Check if only non-code files changed =====
CHANGED_FILES=$(git diff --name-only origin/main...HEAD 2>/dev/null || git diff --name-only HEAD~1...HEAD)
CODE_CHANGES=$(echo "$CHANGED_FILES" | grep -vE '\.(md|txt)$|^docs/' || true)

if [[ -z "$CODE_CHANGES" ]]; then
  echo "  ✓ Skipping version check (only docs/config changes)"
  exit 0
fi

# ===== Version Check =====
echo -n "  Checking version bump... "

# Get versions (plugin.json moved to plugins/ork/ in marketplace restructure)
PLUGIN_JSON="plugins/ork/.claude-plugin/plugin.json"
PR_VERSION=$(jq -r '.version' "$PROJECT_ROOT/$PLUGIN_JSON" 2>/dev/null || echo "")
MAIN_VERSION=$(git show "origin/main:$PLUGIN_JSON" 2>/dev/null | jq -r '.version' 2>/dev/null || echo "")

if [[ -z "$PR_VERSION" ]]; then
  echo "FAILED (cannot read version)"
  exit 1
fi

if [[ -z "$MAIN_VERSION" ]]; then
  echo "OK (new repo or no main branch)"
else
  if [[ "$PR_VERSION" == "$MAIN_VERSION" ]]; then
    echo "FAILED"
    echo ""
    echo "╔════════════════════════════════════════════════════════════╗"
    echo "║  Version not bumped! Current: $MAIN_VERSION"
    echo "╠════════════════════════════════════════════════════════════╣"
    echo "║  Run: ./bin/bump-version.sh patch"
    echo "╚════════════════════════════════════════════════════════════╝"
    exit 1
  fi

  # Verify branch version is GREATER than main (not just different)
  # Sort with -V (version sort) and check that main comes first
  HIGHER=$(printf '%s\n%s\n' "$MAIN_VERSION" "$PR_VERSION" | sort -V | tail -1)
  if [[ "$HIGHER" != "$PR_VERSION" ]]; then
    echo "FAILED"
    echo ""
    echo "╔════════════════════════════════════════════════════════════╗"
    echo "║  Version $PR_VERSION is not greater than main ($MAIN_VERSION)!"
    echo "╠════════════════════════════════════════════════════════════╣"
    echo "║  Did you branch before a release merged to main?"
    echo "║  Run: git fetch origin main && ./bin/bump-version.sh patch"
    echo "╚════════════════════════════════════════════════════════════╝"
    exit 1
  fi

  echo "OK ($MAIN_VERSION → $PR_VERSION)"
fi

# ===== Changelog Check =====
echo -n "  Checking CHANGELOG.md... "

# Note: git diff exits 1 when differences exist; pipefail would break the pipe.
# Capture output first to avoid pipefail interaction.
# Note: pipefail + "echo $big_var | grep -q" causes SIGPIPE when grep exits early.
# Use bash pattern matching instead.
_CL_FILES=$(git diff origin/main --name-only 2>/dev/null </dev/null || true)
if [[ "$_CL_FILES" != *"CHANGELOG.md"* ]]; then
  echo "FAILED"
  echo ""
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║  CHANGELOG.md not updated!"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║  Run: ./bin/bump-version.sh patch"
  echo "║  Then edit CHANGELOG.md with your changes"
  echo "╚════════════════════════════════════════════════════════════╝"
  exit 1
fi
echo "OK"

# ===== Changelog Entry Check =====
echo -n "  Checking CHANGELOG entry for v$PR_VERSION... "

if ! grep -q "^\## \[$PR_VERSION\]" CHANGELOG.md; then
  echo "FAILED"
  echo ""
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║  CHANGELOG.md missing entry for version $PR_VERSION"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║  Add: ## [$PR_VERSION] - $(date +%Y-%m-%d)"
  echo "╚════════════════════════════════════════════════════════════╝"
  exit 1
fi
echo "OK"

# ===== Version Sync Check =====
echo -n "  Checking version sync... "

PLUGIN_V=$(jq -r '.version' plugins/ork/.claude-plugin/plugin.json 2>/dev/null)
MARKET_V=$(jq -r '.version' .claude-plugin/marketplace.json 2>/dev/null)
PYPROJ_V=$(grep -E '^version\s*=' pyproject.toml 2>/dev/null | sed -E 's/.*"([^"]*)".*/\1/')

MISMATCH=0
[[ "$PLUGIN_V" != "$MARKET_V" ]] && MISMATCH=1
[[ "$PLUGIN_V" != "$PYPROJ_V" ]] && MISMATCH=1

if [[ $MISMATCH -eq 1 ]]; then
  echo "FAILED"
  echo "  plugin.json: $PLUGIN_V"
  echo "  marketplace.json: $MARKET_V"
  echo "  pyproject.toml: $PYPROJ_V"
  echo ""
  echo "  Run: ./bin/bump-version.sh patch"
  exit 1
fi
echo "OK (all at v$PLUGIN_V)"

# Cross-platform timeout function (defined early for use in all checks)
run_with_timeout() {
  local timeout_secs=$1
  shift
  if command -v timeout &>/dev/null; then
    timeout "$timeout_secs" "$@"
  elif command -v gtimeout &>/dev/null; then
    gtimeout "$timeout_secs" "$@"
  else
    # No timeout command available (macOS without coreutils).
    # Both perl alarm and background-process watchdog produce unreliable exit
    # codes with npm on macOS. Run directly — timeout protection is only
    # critical on CI where Linux `timeout` is available.
    "$@"
  fi
}

# ===== TypeScript Type Check =====
echo -n "  Running TypeScript type check... "

if [[ -f "src/hooks/tsconfig.json" ]]; then
  if ! run_with_timeout 120 npx tsc --noEmit -p src/hooks/tsconfig.json >/dev/null 2>&1; then
    echo "FAILED"
    echo ""
    echo "╔════════════════════════════════════════════════════════════╗"
    echo "║  TypeScript validation failed!"
    echo "╠════════════════════════════════════════════════════════════╣"
    echo "║  Run: cd src/hooks && npx tsc --noEmit"
    echo "╚════════════════════════════════════════════════════════════╝"
    exit 1
  fi
  echo "OK"
else
  echo "SKIP (no tsconfig.json)"
fi

# ===== Build Verification =====
echo -n "  Verifying build... "

if ! run_with_timeout 300 npm run build >/dev/null 2>&1; then
  echo "FAILED"
  echo ""
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║  Build failed!"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║  Run: npm run build"
  echo "╚════════════════════════════════════════════════════════════╝"
  exit 1
fi
echo "OK"

# ===== Import Verification =====
echo -n "  Verifying imports... "

if [[ -f "scripts/verify-imports.js" ]]; then
  if ! run_with_timeout 60 node scripts/verify-imports.js >/dev/null 2>&1; then
    echo "FAILED"
    echo ""
    echo "╔════════════════════════════════════════════════════════════╗"
    echo "║  Import verification failed!"
    echo "╠════════════════════════════════════════════════════════════╣"
    echo "║  Run: node scripts/verify-imports.js"
    echo "╚════════════════════════════════════════════════════════════╝"
    exit 1
  fi
  echo "OK"
else
  echo "SKIP (verify-imports.js not found)"
fi

# ===== Unit Tests (mirrors CI exactly) =====
echo -n "  Running unit tests... "

# CI test list - must match .github/workflows/ci.yml exactly
CI_UNIT_TESTS=(
  # Core validation tests
  tests/unit/test-json-validity.sh
  tests/unit/test-hook-executability.sh
  tests/unit/test-context-schemas.sh
  tests/unit/test-hooks-unit.sh
  tests/unit/test-shell-syntax.sh
  tests/unit/test-hook-json-output.sh
  tests/unit/test-edge-cases.sh
  tests/unit/test-count-components.sh
  # Hook-specific tests
  tests/unit/test-best-practices.sh
  tests/unit/test-lifecycle-hooks.sh
  tests/unit/test-pretool-all-hooks.sh
  tests/unit/test-pretool-mcp-hooks.sh
  tests/unit/test-pretool-skill-hooks.sh
  tests/unit/test-pretool-task-hooks.sh
  tests/unit/test-permission-posttool-hooks.sh
  tests/unit/test-file-lock-hooks.sh
  tests/unit/test-subagent-hooks.sh
  tests/unit/test-skill-hooks.sh
  tests/unit/test-learning-tracker-hook.sh
  # Memory and context tests
  tests/unit/test-mem0-prompt-hooks.sh
  tests/unit/test-memory-lib.sh
  tests/unit/test-memory-commands.sh
  tests/unit/test-mcp-pretool-hooks.sh
  # Coordination tests
  tests/unit/test-coordination-lib.sh
  tests/unit/test-decision-sync.sh
  tests/unit/test-pattern-sync.sh
  tests/unit/test-worktree-cli.sh
  # Analytics and feedback tests
  tests/unit/test-analytics.sh
  tests/unit/test-feedback-lib.sh
  tests/unit/test-satisfaction-detection.sh
  tests/unit/test-consent-manager.sh
  # Skill and agent unit tests
  tests/unit/test-skill-analytics.sh
  tests/unit/test-skill-edit-tracker.sh
  tests/unit/test-agent-performance.sh
  # Other unit tests
  tests/unit/test-pii-validation.sh
  tests/unit/test-evolution-engine.sh
  tests/unit/test-version-manager.sh
  # Pre-push hook tests
  tests/unit/test-pre-push-hook.sh
)

# Run tests in parallel (~160s sequential → ~75s parallel)
MAX_JOBS=8
if command -v nproc &>/dev/null; then
  CORES=$(nproc)
elif command -v sysctl &>/dev/null; then
  CORES=$(sysctl -n hw.ncpu 2>/dev/null || echo 8)
else
  CORES=8
fi
[[ $CORES -lt $MAX_JOBS ]] && MAX_JOBS=$CORES

export _PREPUSH_FAIL_DIR=$(mktemp -d)
trap 'rm -rf "$_PREPUSH_FAIL_DIR"' EXIT

_run_test() {
  local test_file="$1"
  if [[ -f "$test_file" ]]; then
    if ! bash "$test_file" >/dev/null 2>&1; then
      touch "$_PREPUSH_FAIL_DIR/$(basename "$test_file")"
    fi
  fi
}
export -f _run_test

printf '%s\n' "${CI_UNIT_TESTS[@]}" | xargs -P "$MAX_JOBS" -I {} bash -c '_run_test "$1"' _ {}

FAILURES=()
for f in "$_PREPUSH_FAIL_DIR"/*; do
  [[ -e "$f" ]] && FAILURES+=("$(basename "$f")")
done

if [[ ${#FAILURES[@]} -gt 0 ]]; then
  echo "FAILED"
  for f in "${FAILURES[@]}"; do
    echo "    FAIL: $f"
  done
  echo ""
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║  Unit tests failed! (${#FAILURES[@]} test file(s) failed)"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║  Run failing tests to see details"
  echo "╚════════════════════════════════════════════════════════════╝"
  exit 1
fi
echo "OK (${#CI_UNIT_TESTS[@]} tests, ${MAX_JOBS} parallel)"

# ===== Security Tests =====
echo -n "  Running security tests... "

if [[ -x "tests/security/run-security-tests.sh" ]]; then
  if ! ./tests/security/run-security-tests.sh >/dev/null 2>&1; then
    echo "FAILED"
    echo ""
    echo "╔════════════════════════════════════════════════════════════╗"
    echo "║  Security tests failed!"
    echo "╠════════════════════════════════════════════════════════════╣"
    echo "║  Run: ./tests/security/run-security-tests.sh"
    echo "╚════════════════════════════════════════════════════════════╝"
    exit 1
  fi
  echo "OK"
else
  echo "SKIP (not found)"
fi

# ===== MDX Compile Guard (#1401 — catches nested-fence + other mdx bugs) =====
echo -n "  Running mdx-compile guard... "

if [[ -x "tests/unit/test-mdx-compile.sh" ]]; then
  if ! run_with_timeout 120 ./tests/unit/test-mdx-compile.sh >/dev/null 2>&1; then
    echo "FAILED"
    echo ""
    echo "╔════════════════════════════════════════════════════════════╗"
    echo "║  MDX compile guard failed!"
    echo "╠════════════════════════════════════════════════════════════╣"
    echo "║  Run: npm run test:mdx"
    echo "║  Fix likely needs a 4-backtick outer fence in a SKILL.md"
    echo "╚════════════════════════════════════════════════════════════╝"
    exit 1
  fi
  echo "OK"
else
  echo "SKIP (not found)"
fi

# ===== Agent frontmatter validation =====
echo -n "  Validating agent frontmatter... "

if ! run_with_timeout 60 bash tests/agents/test-agent-frontmatter.sh >/dev/null 2>&1; then
  echo "FAILED"
  echo ""
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║  Agent frontmatter validation failed!"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║  Run: npm run test:agents"
  echo "╚════════════════════════════════════════════════════════════╝"
  exit 1
fi
echo "OK"

# ===== Skill structure validation =====
echo -n "  Validating skill structure... "

if ! run_with_timeout 180 bash tests/skills/structure/test-skill-md.sh >/dev/null 2>&1; then
  echo "FAILED"
  echo ""
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║  Skill structure validation failed!"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║  Run: npm run test:skills"
  echo "╚════════════════════════════════════════════════════════════╝"
  exit 1
fi
echo "OK"

# ===== Manifest consistency =====
echo -n "  Checking manifests... "

MANIFEST_FAILED=0
for check in tests/manifests/test-skill-uniqueness.sh \
             tests/manifests/test-manifest-dependencies.sh \
             tests/manifests/test-plugin-orphan-skills.sh; do
  if [[ -f "$check" ]]; then
    if ! run_with_timeout 30 bash "$check" >/dev/null 2>&1; then
      MANIFEST_FAILED=1
      break
    fi
  fi
done

if [[ $MANIFEST_FAILED -eq 1 ]]; then
  echo "FAILED"
  echo ""
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║  Manifest consistency failed!"
  echo "╠════════════════════════════════════════════════════════════╣"
  echo "║  Run: npm run test:manifests"
  echo "╚════════════════════════════════════════════════════════════╝"
  exit 1
fi
echo "OK"

# ===== Token overhead budget (blocking + warn tier) =====
echo -n "  Checking token overhead budget... "

TOKEN_LOG=$(mktemp)
trap 'rm -f "$TOKEN_LOG"' EXIT

if ! run_with_timeout 30 bash tests/performance/test-token-overhead.sh >"$TOKEN_LOG" 2>&1; then
  echo "FAILED"
  echo ""
  echo "╔════════════════════════════════════════════════════════════╗"
  echo "║  Token overhead exceeds budget!"
  echo "╠════════════════════════════════════════════════════════════╣"
  tail -6 "$TOKEN_LOG" | sed 's/^/║  /'
  echo "╚════════════════════════════════════════════════════════════╝"
  exit 1
fi

# Surface any 90%+ warnings even when the hard check passed
WARN_LINES=$(grep -E '⚠|WARN:' "$TOKEN_LOG" || true)
if [[ -n "$WARN_LINES" ]]; then
  echo "OK (with warnings)"
  echo "$WARN_LINES" | sed 's/^/    /'
else
  echo "OK"
fi


# ===== Hooks vitest suite (catches Bundle-C-style fix-up cycles) =====
# Project lesson from M126 #1543: changes to src/hooks/ trigger a 30+ test
# surface in CI (cross-reference, split-bundles, webhook-forwarder-coverage,
# hooks-json-wiring, dispatcher-registry-wiring). Running the hook unit
# suite locally catches these BEFORE push instead of after a failed CI run.
HOOK_CHANGES=$(echo "$CHANGED_FILES" | grep -E '^src/hooks/' || true)
if [[ -n "$HOOK_CHANGES" ]]; then
  echo -n "  Running hook vitest suite... "
  HOOK_LOG="$(mktemp)"
  trap 'rm -f "$HOOK_LOG"' EXIT
  if ! (cd src/hooks && npx vitest run --reporter=dot >"$HOOK_LOG" 2>&1); then
    echo "FAILED"
    echo ""
    echo "╔════════════════════════════════════════════════════════════╗"
    echo "║  Hooks vitest suite failed locally."
    echo "║  CI runs the same suite — fix before push to avoid round-trips."
    echo "╠════════════════════════════════════════════════════════════╣"
    tail -20 "$HOOK_LOG" | sed 's/^/║  /'
    echo "╚════════════════════════════════════════════════════════════╝"
    exit 1
  fi
  echo "OK"
fi

echo ""
echo "Pre-push validation passed"
exit 0
