#!/bin/bash
# Installation source for the repository pre-commit gate.

REPO_ROOT="$(git rev-parse --show-toplevel)" || exit 1
cd "$REPO_ROOT" || exit 1

IPZ_DIR="plugins/international-press-zone"
TRUSTED_INSTALL_NAME="ipz-remote-gate-hooks"
FAILED=0
NOT_ANALYSED=()
TRUSTED_INSTALL_READY=1

TRUST_DEPTH=5
TRUST_REF="refs/remotes/origin/master"
GATE_PRECOMMIT_SRC=".dev-config/hooks/pre-commit"
GATE_REFERENCE_SRC=".dev-config/hooks/reference-transaction"
GATE_HELPER_SRC=".dev-config/bin/ipz-remote-php-gate"

# Print the blob OIDs of $1 across the last TRUST_DEPTH landed versions on TRUST_REF.
trusted_blobs_for_path() {
    local path=$1 commit blob
    git rev-list -n "$TRUST_DEPTH" "$TRUST_REF" -- "$path" 2>/dev/null |
    while read -r commit; do
        blob=$(git rev-parse -q --verify "$commit:$path" 2>/dev/null) || continue
        printf '%s\n' "$blob"
    done
}

# Installed file $1 must byte-match some landed version of source path $2.
installed_blob_is_trusted() {
    local active=$1 tracked=$2 active_oid blob matched=1
    active_oid=$(git hash-object --no-filters "$active" 2>/dev/null) || return 1
    while read -r blob; do
        [ -n "$blob" ] && [ "$active_oid" = "$blob" ] && matched=0
    done < <(trusted_blobs_for_path "$tracked")
    return "$matched"
}

GATE_PROTECTED_PATHS=(
    ".dev-config/hooks/"
    ".dev-config/bin/ipz-remote-php-gate"
    ".dev-config/bin/install-ipz-gate"
    ".dev-config/bin/approve-gate-change"
    ".github/"
)

fail() {
    echo "❌ $*" >&2
    FAILED=1
}

note_unanalysed() {
    NOT_ANALYSED+=("$1")
}

burn_file() {
    local path=$1 burned
    [ ! -L "$path" ] || return 1
    [ -e "$path" ] || return 0
    burned="$path.burned.$$.$RANDOM"
    mv "$path" "$burned" || return 1
    rm -f "$burned"
}

validate_installed_source() {
    local install_dir=$1 name=$2 active resolved
    active="$install_dir/$name"
    [ -f "$active" ] && [ ! -L "$active" ] && [ -x "$active" ] || return 1
    resolved=$(realpath "$active" 2>/dev/null) || return 1
    [ "$resolved" = "$active" ] || return 1
    [ "$(stat -c %u "$active" 2>/dev/null)" = "$(id -u)" ] || return 1
    [ "$(stat -c %a "$active" 2>/dev/null)" = 500 ] || return 1
}

owner_token_is_valid() {
    local token=$1 tree=$2 expected
    [ -f "$token" ] && [ ! -L "$token" ] || return 1
    [ "$(stat -c %u "$token" 2>/dev/null)" = "$(id -u)" ] || return 1
    [ "$(stat -c %a "$token" 2>/dev/null)" = 400 ] || return 1
    case "$tree" in *[!0-9a-f]*|'') return 1 ;; esac
    case "${#tree}" in 40|64) ;; *) return 1 ;; esac
    expected="approve-gate-change $tree"
    printf '%s\n' "$expected" | cmp -s - "$token"
}

resolve_trusted_install() {
    local common configured rc top version mode
    common=$(git rev-parse --git-common-dir 2>/dev/null) || return 1
    common=$(realpath "$common" 2>/dev/null) || return 1
    configured=$(git config --path --get core.hooksPath 2>/dev/null)
    rc=$?
    [ "$rc" -eq 0 ] && [ -n "$configured" ] || return 1
    if [ "${configured#/}" = "$configured" ]; then configured="$REPO_ROOT/$configured"; fi
    [ ! -L "$configured" ] || return 1
    configured=$(realpath "$configured" 2>/dev/null) || return 1
    top="$common/$TRUSTED_INSTALL_NAME"
    if [ "$configured" = "$top" ]; then
        mode=700
    else
        [ -d "$top" ] && [ ! -L "$top" ] || return 1
        [ "$(stat -c %u "$top" 2>/dev/null)" = "$(id -u)" ] || return 1
        [ "$(stat -c %a "$top" 2>/dev/null)" = 700 ] || return 1
        version=${configured#"$top/"}
        [ "$configured" = "$top/$version" ] || return 1
        [[ "$version" =~ ^[0-9a-f]{12}$ ]] || return 1
        mode=500
    fi
    TRUSTED_INSTALL_DIR="$configured"
    [ -d "$configured" ] && [ ! -L "$configured" ] || return 1
    [ "$(stat -c %u "$configured" 2>/dev/null)" = "$(id -u)" ] || return 1
    [ "$(stat -c %a "$configured" 2>/dev/null)" = "$mode" ] || return 1
    [ "$(realpath "${BASH_SOURCE[0]}" 2>/dev/null)" = "$configured/pre-commit" ] || return 1
    validate_installed_source "$configured" pre-commit || return 1
    installed_blob_is_trusted "$configured/pre-commit" "$GATE_PRECOMMIT_SRC" || return 1
    validate_installed_source "$configured" reference-transaction || return 1
    installed_blob_is_trusted "$configured/reference-transaction" "$GATE_REFERENCE_SRC" || return 1
    validate_installed_source "$configured" ipz-remote-php-gate || return 1
    installed_blob_is_trusted "$configured/ipz-remote-php-gate" "$GATE_HELPER_SRC" || return 1
}

if ! resolve_trusted_install; then
    fail "Trusted IPZ gate installation is missing, unsafe, misconfigured, or does not match landed sources"
    TRUSTED_INSTALL_READY=0
fi

DISCOVERY_DIR=$(mktemp -d) || {
    echo "❌ Pre-commit gate could not create its staged-path workspace." >&2
    exit 1
}
cleanup_discovery() {
    local rc=${1:-$?}
    trap - EXIT HUP INT TERM
    rm -rf "$DISCOVERY_DIR"
    exit "$rc"
}
trap 'cleanup_discovery $?' EXIT
trap 'cleanup_discovery 129' HUP
trap 'cleanup_discovery 130' INT
trap 'cleanup_discovery 143' TERM

discover_staged_paths() {
    local output=$1 failure=$2
    shift 2
    if ! git diff --cached -z --name-only "$@" >"$output"; then
        fail "$failure"
        return 1
    fi
}

INITIAL_TREE=$(git write-tree 2>/dev/null)
rc=$?
[ "$rc" -eq 0 ] && [ -n "$INITIAL_TREE" ] || fail 'Initial staged tree could not be recorded'

OWNER_TOKEN=""
BURN_OWNER_TOKEN=0
PROTECTED_STAGED_PATHS=()
if discover_staged_paths "$DISCOVERY_DIR/protected" 'Protected staged-path discovery failed' \
    --diff-filter=ACMRTD -- "${GATE_PROTECTED_PATHS[@]}"; then
    mapfile -d '' -t PROTECTED_STAGED_PATHS <"$DISCOVERY_DIR/protected" || fail 'Protected staged-path results could not be loaded'
fi
if [ ${#PROTECTED_STAGED_PATHS[@]} -gt 0 ]; then
    common=$(git rev-parse --git-common-dir 2>/dev/null)
    rc=$?
    if [ "$rc" -ne 0 ] || [ -z "$common" ]; then
        fail 'Gate-source changes require owner approval: run .dev-config/bin/approve-gate-change'
    else
        common=$(realpath "$common" 2>/dev/null)
        if [ -z "$common" ]; then
            fail 'Gate-source changes require owner approval: run .dev-config/bin/approve-gate-change'
        else
            OWNER_TOKEN="$common/ipz-gate-owner-token"
            if owner_token_is_valid "$OWNER_TOKEN" "$INITIAL_TREE"; then
                BURN_OWNER_TOKEN=1
            else
                fail 'Gate-source changes require owner approval: run .dev-config/bin/approve-gate-change'
            fi
        fi
    fi
fi
if ! git ls-files --stage -z >"$DISCOVERY_DIR/index.before"; then
    fail 'Initial staged path manifest could not be recorded'
fi

echo "🛑 Running Pre-Commit Checks..."

# 1. JavaScript (ESLint) — staged JS/TS only
LINT_BIN=".dev-config/bin/lint"

STAGED_JS=()
if discover_staged_paths "$DISCOVERY_DIR/javascript" 'JavaScript staged-path discovery failed' \
    --diff-filter=ACM -- '*.js' '*.jsx' '*.ts' '*.tsx' '*.mjs' '*.cjs'; then
    mapfile -d '' -t STAGED_JS <"$DISCOVERY_DIR/javascript" || fail 'JavaScript staged-path results could not be loaded'
fi
if [ ${#STAGED_JS[@]} -gt 0 ]; then
    if [ ! -x "$LINT_BIN" ]; then
        fail "ESLint gate could not run: $LINT_BIN is missing from the checkout"
    else
        echo "🔍 ESLint — ${#STAGED_JS[@]} staged file(s)..."
        "$LINT_BIN" --max-warnings 9999 -- "${STAGED_JS[@]}"
        rc=$?
        case "$rc" in
            0) ;;
            1) fail "ESLint reported problems in staged files" ;;
            *) fail "ESLint could not run (exit $rc)" ;;
        esac
    fi
else
    note_unanalysed "JavaScript/TypeScript — no staged files"
fi

# 2. Remote PHP and convention analysis — scoped to international-press-zone
STAGED_PHP=()
STAGED_IPZ=()
if discover_staged_paths "$DISCOVERY_DIR/php" 'PHP staged-path discovery failed' --diff-filter=ACMRT -- '*.php'; then
    mapfile -d '' -t STAGED_PHP <"$DISCOVERY_DIR/php" || fail 'PHP staged-path results could not be loaded'
fi
if discover_staged_paths "$DISCOVERY_DIR/ipz" 'IPZ convention staged-path discovery failed' \
    --diff-filter=ACMRT -- "$IPZ_DIR/includes" "$IPZ_DIR/admin/src"; then
    mapfile -d '' -t STAGED_IPZ <"$DISCOVERY_DIR/ipz" || fail 'IPZ convention staged-path results could not be loaded'
fi

IPZ_PHP=()
OTHER_PHP=()
for f in "${STAGED_PHP[@]}"; do
    case "$f" in
        "$IPZ_DIR"/*) IPZ_PHP+=("${f#"$IPZ_DIR"/}") ;;
        *) OTHER_PHP+=("$f") ;;
    esac
done

IPZ_SLOPGATE=()
for f in "${STAGED_IPZ[@]}"; do
    case "$f" in
        *.php|*.js|*.jsx|*.scss) IPZ_SLOPGATE+=("${f#"$IPZ_DIR"/}") ;;
    esac
done

if [ ${#OTHER_PHP[@]} -gt 0 ]; then
    note_unanalysed "PHP — ${#OTHER_PHP[@]} staged file(s) outside $IPZ_DIR (no static-analysis config exists for them)"
fi

if [ ${#IPZ_PHP[@]} -eq 0 ] && [ ${#IPZ_SLOPGATE[@]} -eq 0 ]; then
    note_unanalysed "Remote IPZ gate — no applicable files at initial discovery"
fi

# 3. Filesystem vulnerabilities / secrets
if command -v trivy &>/dev/null; then
    echo "🔍 Trivy — filesystem scan..."
    trivy fs . --scanners vuln,secret,misconfig --exit-code 1 || fail "Trivy reported findings"
else
    note_unanalysed "Trivy — binary not installed"
fi

# 4. Composer dependency audit
if [ -f "composer.lock" ]; then
    if command -v composer &>/dev/null; then
        echo "🔍 Composer audit..."
        composer audit || fail "Composer audit reported advisories"
    else
        note_unanalysed "Composer audit — composer binary not installed"
    fi
else
    note_unanalysed "Composer audit — no composer.lock at repository root"
fi

FINAL_TREE=$(git write-tree 2>/dev/null)
rc=$?
if [ "$rc" -ne 0 ] || [ -z "$FINAL_TREE" ]; then
    fail 'Final staged tree could not be recorded'
elif [ "$FINAL_TREE" != "$INITIAL_TREE" ]; then
    fail 'Staged tree changed while pre-commit checks ran'
fi
if ! git ls-files --stage -z >"$DISCOVERY_DIR/index.after"; then
    fail 'Final staged path manifest could not be recorded'
elif ! cmp -s "$DISCOVERY_DIR/index.before" "$DISCOVERY_DIR/index.after"; then
    fail 'Staged path manifest changed while pre-commit checks ran'
fi

FINAL_STAGED_PHP=()
FINAL_STAGED_IPZ=()
if discover_staged_paths "$DISCOVERY_DIR/php.final" 'Final PHP staged-path discovery failed' --diff-filter=ACMRT -- '*.php'; then
    mapfile -d '' -t FINAL_STAGED_PHP <"$DISCOVERY_DIR/php.final" || fail 'Final PHP staged-path results could not be loaded'
fi
if discover_staged_paths "$DISCOVERY_DIR/ipz.final" 'Final IPZ convention staged-path discovery failed' \
    --diff-filter=ACMRT -- "$IPZ_DIR/includes" "$IPZ_DIR/admin/src"; then
    mapfile -d '' -t FINAL_STAGED_IPZ <"$DISCOVERY_DIR/ipz.final" || fail 'Final IPZ convention staged-path results could not be loaded'
fi

FINAL_IPZ_PHP=()
for f in "${FINAL_STAGED_PHP[@]}"; do
    case "$f" in "$IPZ_DIR"/*) FINAL_IPZ_PHP+=("${f#"$IPZ_DIR"/}") ;; esac
done
FINAL_IPZ_SLOPGATE=()
for f in "${FINAL_STAGED_IPZ[@]}"; do
    case "$f" in *.php|*.js|*.jsx|*.scss) FINAL_IPZ_SLOPGATE+=("${f#"$IPZ_DIR"/}") ;; esac
done

if [ "$FAILED" -eq 0 ]; then
    REMOTE_GATE="${TRUSTED_INSTALL_DIR-}/ipz-remote-php-gate"
    if [ "$TRUSTED_INSTALL_READY" -ne 1 ]; then
        fail 'Final staged tree cannot be claimed without a valid trusted installation'
    elif [ ! -x "$REMOTE_GATE" ]; then
        fail 'Final staged tree cannot be claimed: trusted helper is missing or not executable'
    elif [ ${#FINAL_IPZ_PHP[@]} -gt 0 ] || [ ${#FINAL_IPZ_SLOPGATE[@]} -gt 0 ]; then
        echo "🔍 Remote PHPCS, PHPStan, and slopgate (international-press-zone)..."
        "$REMOTE_GATE"
        rc=$?
        [ "$rc" -eq 0 ] || fail "Remote IPZ gate could not complete (exit $rc)"
    else
        echo "🔒 Binding final staged tree to this commit..."
        "$REMOTE_GATE" --claim-only
        rc=$?
        [ "$rc" -eq 0 ] || fail "Final staged tree claim could not be created (exit $rc)"
    fi
fi

if [ "$FAILED" -ne 0 ]; then
    echo "❌ Pre-commit gate FAILED." >&2
    exit 1
fi

echo "✅ Every check that applies to the staged changes passed."
for entry in "${NOT_ANALYSED[@]}"; do
    echo "ℹ️  Not analysed: $entry"
done
if [ "$BURN_OWNER_TOKEN" -eq 1 ] && ! burn_file "$OWNER_TOKEN"; then
    echo "❌ Gate-source owner approval token could not be burned." >&2
    exit 1
fi
exit 0
