#!/usr/bin/env bash
# Test suite for scripts/detect-changes.sh
# Requires bash 4+ (same as the script under test).
# Run: bash scripts/__tests__/detect-changes.test.sh
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="$SCRIPT_DIR/detect-changes.sh"
PASS=0
FAIL=0
ORIG_DIR="$(pwd)"

# ── Helpers ──────────────────────────────────────────────────────────────

setup_workspace() {
  WORK_DIR=$(mktemp -d)
  cd "$WORK_DIR"
  git init -q
  git config user.email "test@test.com"
  git config user.name "Test"
  git config commit.gpgsign false

  cat > package.json <<< '{ "name": "test-monorepo", "private": true }'
  cat > pnpm-workspace.yaml <<< $'packages:\n  - apps/*\n  - packages/*'
  cat > pnpm-lock.yaml <<< "lockfileVersion: '9.0'"

  mkdir -p packages/common/src packages/api/src packages/ext/src apps/server/src apps/worker/src

  cat > packages/common/package.json <<< '{ "name": "@test/common", "dependencies": {} }'
  echo "export const x = 1;" > packages/common/src/index.ts

  cat > packages/api/package.json <<< '{ "name": "@test/api", "dependencies": {} }'
  echo "export const y = 1;" > packages/api/src/index.ts

  cat > packages/ext/package.json <<< '{ "name": "@test/ext", "dependencies": { "@test/common": "workspace:*" } }'
  echo "export {};" > packages/ext/src/index.ts

  cat > apps/server/package.json <<< '{ "name": "@test/server", "dependencies": { "@test/common": "workspace:*", "@test/api": "workspace:*", "@test/ext": "workspace:*" } }'
  echo "export {};" > apps/server/src/index.ts

  cat > apps/worker/package.json <<< '{ "name": "@test/worker", "dependencies": { "@test/common": "workspace:*" } }'
  echo "export {};" > apps/worker/src/index.ts

  git add -A && git commit -q -m "initial"
}

teardown() {
  cd "$ORIG_DIR"
  rm -rf "$WORK_DIR"
}

run_detect() {
  DIFF_BASE="${1:-HEAD~1}" bash "$SCRIPT" 2>/dev/null
}

# Patched version with test gate regexes
run_detect_patched() {
  local base="${1:-HEAD~1}"
  local patched
  patched=$(mktemp)
  sed \
    -e 's|SERVER_GATE_RE=.*|SERVER_GATE_RE='"'"'^@test/server$'"'"'|' \
    -e 's|SEARCH_GATE_RE=.*|SEARCH_GATE_RE='"'"'^@test/search$'"'"'|' \
    -e 's|RUNNER_GATE_RE=.*|RUNNER_GATE_RE='"'"'^@test/worker$'"'"'|' \
    "$SCRIPT" > "$patched"
  DIFF_BASE="$base" bash "$patched" 2>/dev/null
  rm -f "$patched"
}

extract() {
  echo "$1" | grep "^${2}=" | sed "s/^${2}=//"
}

assert_eq() {
  if [[ "$2" == "$3" ]]; then
    PASS=$((PASS + 1)); echo "  PASS: $1"
  else
    FAIL=$((FAIL + 1)); echo "  FAIL: $1"; echo "    expected: $2"; echo "    actual:   $3"
  fi
}

assert_contains() {
  if [[ "$3" == *"$2"* ]]; then
    PASS=$((PASS + 1)); echo "  PASS: $1"
  else
    FAIL=$((FAIL + 1)); echo "  FAIL: $1"; echo "    expected to contain: $2"; echo "    actual: $3"
  fi
}

assert_not_contains() {
  if [[ "$3" != *"$2"* ]]; then
    PASS=$((PASS + 1)); echo "  PASS: $1"
  else
    FAIL=$((FAIL + 1)); echo "  FAIL: $1"; echo "    expected NOT to contain: $2"; echo "    actual: $3"
  fi
}

# ── Tests ────────────────────────────────────────────────────────────────

test_no_changes() {
  echo "test: no changes"
  setup_workspace
  local out; out=$(run_detect HEAD)
  assert_eq "empty PNPM_FILTER" "" "$(extract "$out" PNPM_FILTER)"
  assert_eq "SERVER_CHANGED=false" "false" "$(extract "$out" SERVER_CHANGED)"
  assert_eq "SEARCH_CHANGED=false" "false" "$(extract "$out" SEARCH_CHANGED)"
  assert_eq "RUNNER_CHANGED=false" "false" "$(extract "$out" RUNNER_CHANGED)"
  teardown
}

test_single_package_change() {
  echo "test: single package change"
  setup_workspace
  echo "// change" >> packages/api/src/index.ts
  git add -A && git commit -q -m "change api"
  local out; out=$(run_detect HEAD~1)
  local f; f=$(extract "$out" PNPM_FILTER)
  assert_contains "filter has @test/api..." "@test/api..." "$f"
  assert_not_contains "filter excludes common" "@test/common" "$f"
  teardown
}

test_transitive_propagation() {
  echo "test: transitive propagation (deploy gates)"
  setup_workspace
  echo "// change" >> packages/common/src/index.ts
  git add -A && git commit -q -m "change common"
  local out; out=$(run_detect_patched HEAD~1)
  local f; f=$(extract "$out" PNPM_FILTER)
  assert_contains "filter has @test/common..." "@test/common..." "$f"
  # common → ext → server (transitive)
  assert_eq "SERVER_CHANGED=true" "true" "$(extract "$out" SERVER_CHANGED)"
  # common → worker (direct)
  assert_eq "RUNNER_CHANGED=true (worker)" "true" "$(extract "$out" RUNNER_CHANGED)"
  teardown
}

test_root_change() {
  echo "test: root-level change"
  setup_workspace
  echo "{}" > tsconfig.base.json
  git add -A && git commit -q -m "change root config"
  local out; out=$(run_detect HEAD~1)
  local f; f=$(extract "$out" PNPM_FILTER)
  assert_contains "has @test/common" "@test/common" "$f"
  assert_contains "has @test/api" "@test/api" "$f"
  assert_contains "has @test/ext" "@test/ext" "$f"
  assert_contains "has @test/server" "@test/server" "$f"
  assert_contains "has @test/worker" "@test/worker" "$f"
  teardown
}

test_infra_change() {
  echo "test: infra change sets all deploy gates"
  setup_workspace
  echo "v2" > pnpm-lock.yaml
  git add -A && git commit -q -m "bump lockfile"
  local out; out=$(run_detect_patched HEAD~1)
  assert_eq "SERVER_CHANGED=true" "true" "$(extract "$out" SERVER_CHANGED)"
  assert_eq "SEARCH_CHANGED=true" "true" "$(extract "$out" SEARCH_CHANGED)"
  assert_eq "RUNNER_CHANGED=true" "true" "$(extract "$out" RUNNER_CHANGED)"
  teardown
}

test_invalid_base() {
  echo "test: invalid base ref → full build fallback"
  setup_workspace
  local out; out=$(run_detect "nonexistent-ref")
  local f; f=$(extract "$out" PNPM_FILTER)
  assert_contains "fallback has @test/common" "@test/common" "$f"
  assert_contains "fallback has @test/server" "@test/server" "$f"
  assert_eq "SERVER_CHANGED=true" "true" "$(extract "$out" SERVER_CHANGED)"
  assert_eq "SEARCH_CHANGED=true" "true" "$(extract "$out" SEARCH_CHANGED)"
  assert_eq "RUNNER_CHANGED=true" "true" "$(extract "$out" RUNNER_CHANGED)"
  teardown
}

test_multiple_changes() {
  echo "test: multiple packages changed"
  setup_workspace
  echo "// a" >> packages/api/src/index.ts
  echo "// b" >> apps/worker/src/index.ts
  git add -A && git commit -q -m "change api and worker"
  local out; out=$(run_detect HEAD~1)
  local f; f=$(extract "$out" PNPM_FILTER)
  assert_contains "filter has @test/api..." "@test/api..." "$f"
  assert_contains "filter has @test/worker..." "@test/worker..." "$f"
  teardown
}

test_dotfile_change() {
  echo "test: dotfile change → all packages"
  setup_workspace
  echo "{}" > .prettierrc
  git add -A && git commit -q -m "add prettierrc"
  local out; out=$(run_detect HEAD~1)
  local f; f=$(extract "$out" PNPM_FILTER)
  assert_contains "has @test/common" "@test/common" "$f"
  assert_contains "has @test/server" "@test/server" "$f"
  teardown
}

test_platform_gate() {
  echo "test: platform gate (library change triggers SERVER_CHANGED)"
  setup_workspace
  # Add a platform app that depends on common (like @coda/platform → @coda/common)
  mkdir -p apps/platform/src
  cat > apps/platform/package.json <<< '{ "name": "@test/platform", "dependencies": { "@test/common": "workspace:*" } }'
  echo "export {};" > apps/platform/src/index.ts
  git add -A && git commit -q -m "add platform"

  # Patch gate config: platform triggers server deploy
  local patched; patched=$(mktemp)
  sed \
    -e 's|SERVER_GATE_RE=.*|SERVER_GATE_RE='"'"'^(@test/server\|@test/platform)$'"'"'|' \
    -e 's|SEARCH_GATE_RE=.*|SEARCH_GATE_RE='"'"'^@test/search$'"'"'|' \
    -e 's|RUNNER_GATE_RE=.*|RUNNER_GATE_RE='"'"'^@test/worker$'"'"'|' \
    "$SCRIPT" > "$patched"

  # Change common → should propagate to platform → SERVER_CHANGED
  echo "// change" >> packages/common/src/index.ts
  git add -A && git commit -q -m "change common"

  local out; out=$(DIFF_BASE=HEAD~1 bash "$patched" 2>/dev/null)
  assert_eq "SERVER_CHANGED=true (platform gate)" "true" "$(extract "$out" SERVER_CHANGED)"
  rm -f "$patched"
  teardown
}

# ── Run ──────────────────────────────────────────────────────────────────

echo "Running detect-changes.sh tests..."
echo ""

test_no_changes
test_single_package_change
test_transitive_propagation
test_root_change
test_infra_change
test_invalid_base
test_multiple_changes
test_dotfile_change
test_platform_gate

echo ""
echo "────────────────────────────"
echo "Results: $PASS passed, $FAIL failed"
[[ $FAIL -eq 0 ]]
