#!/usr/bin/env bash
#
# Run the checks that gate a merge. CI runs this exact script, so a green run here means a
# green run on your PR -- there is no second code path to drift out of sync.
#
# Two prerequisites: uv and terraform. uv fetches Python, pyyaml, cfn-lint and the
# builder-mcp test dependencies on demand and caches them, so nothing Python is installed
# globally and there is no venv to activate. Terraform has to be a real install -- it is a
# single binary and there is no uv equivalent.

set -euo pipefail

# Pinned so local and CI resolve the same linter. Bump deliberately, in a PR.
CFN_LINT='cfn-lint>=1.53,<2'
REGION='us-east-1'

cd "$(dirname "$0")/.."

if ! command -v uv >/dev/null 2>&1; then
  cat >&2 <<'MSG'
error: uv is required and was not found.

  macOS:  brew install uv
  other:  curl -LsSf https://astral.sh/uv/install.sh | sh

uv fetches Python, pyyaml and cfn-lint itself.
MSG
  exit 127
fi

if ! command -v terraform >/dev/null 2>&1; then
  cat >&2 <<'MSG'
error: terraform is required and was not found.

  macOS:  brew tap hashicorp/tap && brew install hashicorp/tap/terraform
  other:  https://developer.hashicorp.com/terraform/install

Needed to check the Azure/Entra modules under blueprints/*/infra/azure. CI installs it with
hashicorp/setup-terraform, the one non-github-owned action the org policy permits.
MSG
  exit 127
fi

echo '==> stack registry'
uv run --quiet pipeline/validate_stacks.py

echo '==> template size'
# CloudFormation rejects a template over 51,200 bytes when it arrives in a request body, which is
# how every by-hand `aws cloudformation deploy --template-file` sends it. knowledgebase.yml crossed
# that line once already and the failure happens before the request reaches AWS, so it is invisible
# to cfn-lint and to a builder with no account access. 48,000 leaves room to add a resource without
# tripping it; past that, move comment prose into the blueprint's docs/ rather than deleting it.
#
# The pipeline stages templates through S3, where the ceiling is 1 MB, so this guards the rehearsal
# path rather than the merge path. The rehearsal path is the one nobody notices breaking.
size_warn=48000
size_max=51200
size_failed=0
for t in $(uv run --quiet pipeline/validate_stacks.py --list); do
  bytes=$(wc -c <"$t" | tr -d ' ')
  if [ "$bytes" -ge "$size_max" ]; then
    printf 'error: %s is %s bytes, at or over the %s-byte request-body limit\n' "$t" "$bytes" "$size_max" >&2
    printf '       by-hand deploys of it now require --s3-bucket\n' >&2
    size_failed=1
  elif [ "$bytes" -ge "$size_warn" ]; then
    printf '  %s: %s bytes (limit %s -- getting close)\n' "$t" "$bytes" "$size_max"
  fi
done
[ "$size_failed" -eq 0 ] || exit 1
echo 'template size: under the request-body limit'

echo
echo '==> cfn-lint'
# Template paths come from the registry and contain no spaces, so word splitting is what we
# want here: cfn-lint takes them as separate arguments.
templates=$(uv run --quiet pipeline/validate_stacks.py --list)
# The -- is required. cfn-lint's --region takes nargs='+', so without it these paths are
# parsed as region names, nothing is linted, and the command still exits 0.
# shellcheck disable=SC2086
uvx --quiet --from "$CFN_LINT" cfn-lint --region "$REGION" -- $templates
echo 'cfn-lint: clean'

echo
echo '==> builder-mcp tests'
# Run from the package directory rather than with `uv run --project`: pytest resolves the
# `testpaths` in that package's pyproject.toml against its own rootdir, and rootdir follows the
# invocation directory. Its .python-version pins the interpreter, so uv fetches a 64-bit CPython
# instead of using whatever happens to be on PATH -- a 32-bit Python has no cryptography wheel
# and sends the install into a Rust build that fails.
(
  cd packages/builder-mcp
  uv run --quiet pytest -q
)

echo
echo '==> teams-bot tests'
# Added because these were invisible to the PR gate: the suite existed, passed, and gated nothing,
# so "tools/check is green" and "the tests pass" were two different claims that read as one.
#
# Dependencies are named inline rather than taken from the blueprint's requirements.lock: that lock
# is the Lambda image's runtime set and carries no test dependencies, and the blueprint has no
# pyproject.toml to resolve a dev group from. The versions here track requirements.txt -- if that
# file's ranges change, change these too.
uv run --quiet --python 3.13 \
  --with 'PyJWT[crypto]>=2.8,<3' --with 'anthropic>=0.92,<2' --with boto3 --with pytest \
  pytest blueprints/teams-bot/tests -q

# Module list comes from the same script that enforces the pipeline mirroring, so there is one
# definition of "a Terraform module in this repo" rather than a glob duplicated here.
modules=$(uv run --quiet pipeline/validate_stacks.py --list-tf)

if [ -z "$modules" ]; then
  echo
  echo '==> terraform'
  echo 'terraform: no modules to check'
else
  echo
  echo '==> terraform fmt'
  # -recursive so a module with subdirectories is covered too. -check does not rewrite.
  echo "$modules" | while IFS= read -r module; do
    terraform fmt -check -recursive -no-color "$module"
  done
  echo 'terraform fmt: clean'

  echo
  echo '==> terraform validate'
  # -backend=false is what makes this runnable with no AWS credentials and no state access:
  # it skips backend initialization while still resolving providers, so a bad resource
  # attribute or an unknown provider argument is caught here rather than after merge.
  echo "$modules" | while IFS= read -r module; do
    echo "  $module"
    terraform -chdir="$module" init -backend=false -input=false -no-color >/dev/null
    terraform -chdir="$module" validate -no-color
  done
  echo 'terraform validate: clean'
fi

echo
echo 'all checks passed'
