#!/usr/bin/env bash
# python_gate — pre-execution schema enforcement interceptor
# This script intercepts 'python' calls and validates code before running it.
#
# Controlled by $GATEHOUSE_MODE:
#   hard  — block execution on violations (LLM enforcement)
#   soft  — print violations but always run (developer visibility)
#   off   — pass-through, no checking (default when unset)

# Discover own location — works whether symlinked, aliased, or installed to PATH
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")")" && pwd)"

# Real Python interpreter — found dynamically, never hardcoded
REAL_PYTHON="$(command -v python3)"

# Enforcement mode: off (default), hard, soft
MODE="${GATEHOUSE_MODE:-off}"

# Normalise to lowercase
MODE=$(echo "$MODE" | tr '[:upper:]' '[:lower:]')

# Off or unset — pass through immediately, zero overhead
if [[ "$MODE" != "hard" && "$MODE" != "soft" ]]; then
    exec $REAL_PYTHON "$@"
fi

# Gate home: try explicit override, then script directory, then Python module discovery
if [[ -n "${GATE_HOME:-}" ]] && [[ -f "${GATE_HOME}/engine.py" ]]; then
    : # Use explicit GATE_HOME — user override takes priority
elif [[ -f "${SCRIPT_DIR}/engine.py" ]]; then
    GATE_HOME="$SCRIPT_DIR"  # Dev / source checkout — engine is next to interceptor
else
    # pip install: engine.py lives in site-packages/gatehouse/, not next to python_gate.
    # Ask the same Python interpreter to locate the installed package.
    GATE_HOME=$($REAL_PYTHON -c "import os, gatehouse.engine as ge; print(os.path.dirname(os.path.abspath(ge.__file__)))" 2>/dev/null)
fi

# Export so gate_engine.py receives the same home directory we discovered.
# Without this, gate_engine falls back to Path(__file__).resolve().parent which
# can differ due to symlinks, .pyc caching, or pip path indirection.
export GATE_HOME

# Ensure the gatehouse package is importable.  In a dev / source checkout,
# GATE_HOME is  …/src/gatehouse/ so its grandparent (…/src/) must be on
# PYTHONPATH.  When pip-installed, the package is already in site-packages.
_SRC_ROOT="$(dirname "$(dirname "$GATE_HOME")")"
case ":${PYTHONPATH:-}:" in
    *":${_SRC_ROOT}:"*) ;;   # Already present
    *) export PYTHONPATH="${_SRC_ROOT}${PYTHONPATH:+:$PYTHONPATH}" ;;
esac

# We check the file exists to confirm GATE_HOME is valid, but we invoke
# via -m so that Python resolves the package correctly (__package__ is set,
# all relative and absolute package imports work reliably).
GATE_ENGINE="${GATE_HOME}/engine.py"

# Find the .py file in the command arguments
PY_FILE=""
for arg in "$@"; do
    if [[ "$arg" == *.py ]]; then
        PY_FILE="$arg"
        break
    fi
done

# If no .py file or no gate engine, just run Python normally
if [[ -z "$PY_FILE" ]] || [[ ! -f "$GATE_ENGINE" ]]; then
    exec $REAL_PYTHON "$@"
fi

# Walk up from the .py file's directory looking for .gate_schema.yaml
# This is how python_gate knows which project (and schema) to use
DIR=$(dirname "$(realpath "$PY_FILE")")
SCHEMA=""
while [[ "$DIR" != "/" ]]; do
    if [[ -f "$DIR/.gate_schema.yaml" ]]; then
        SCHEMA="$DIR/.gate_schema.yaml"
        break
    fi
    DIR=$(dirname "$DIR")
done

# No .gate_schema.yaml found? This project isn't gated. Run normally.
if [[ -z "$SCHEMA" ]]; then
    exec $REAL_PYTHON "$@"
fi

# Read schema name for the banner (best-effort grep from the config)
SCHEMA_NAME=$(grep -m1 '^schema:' "$SCHEMA" 2>/dev/null | sed 's/schema:[[:space:]]*//' | tr -d '"' | tr -d "'")
SCHEMA_NAME="${SCHEMA_NAME:-unknown}"

# Print activation banner to stderr (always visible, never corrupts stdout)
if [[ "$MODE" == "hard" ]]; then
    MODE_LABEL="\033[91mHARD\033[0m"
else
    MODE_LABEL="\033[93mSOFT\033[0m"
fi

{
    echo -e "\033[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m"
    echo -e "  \033[1mGATEHOUSE ACTIVE\033[0m  |  Mode: ${MODE_LABEL}  |  Schema: \033[96m${SCHEMA_NAME}\033[0m"
    echo -e "  \033[2mDeactivate: export GATEHOUSE_MODE=off\033[0m"
    echo -e "\033[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m"
} >&2

# Run the gate engine to check the code
# --no-scope: python_gate intercepted an explicit python invocation, so skip
# gated_paths filtering — the user asked to run this file, enforce it.
# Mark file as scanned by the outer shim so the inner import hook
# (gatehouse.auto) skips it — prevents double-scanning.
export GATEHOUSE_OUTER_VERDICT="${GATEHOUSE_OUTER_VERDICT:+${GATEHOUSE_OUTER_VERDICT}:}$(realpath "$PY_FILE")"

GATE_OUTPUT=$($REAL_PYTHON -m gatehouse.engine --file "$PY_FILE" --schema "$SCHEMA" --no-scope 2>&1)
GATE_EXIT=$?

# If violations were found, print the gate output to stderr
if [[ -n "$GATE_OUTPUT" ]]; then
    echo "$GATE_OUTPUT" >&2
fi

# Mode-specific behavior
if [[ $GATE_EXIT -ne 0 ]]; then
    if [[ "$MODE" == "hard" ]]; then
        # Hard mode: block execution — the LLM must fix the code
        exit $GATE_EXIT
    else
        # Soft mode: print a reminder that hard mode would have blocked, then run anyway
        {
            echo ""
            echo -e "  \033[93m▸ Soft mode: execution continues. Hard mode would have BLOCKED.\033[0m"
            echo -e "  \033[2m  Switch: export GATEHOUSE_MODE=hard\033[0m"
            echo ""
        } >&2
    fi
fi

# Gate passed (or soft mode) — run the code with real Python
exec $REAL_PYTHON "$@"
