#!/usr/bin/env bash
#
# O PRE-PUSH, POSTO A PROVA -- D442, desde 07/10/2026.
#
#     bash scripts/ensaiar-pre-push.sh
#
# POR QUE ISTO EXISTE. O `.githooks/pre-push` e o ultimo portao antes de um
# segredo chegar ao remoto, e nenhum roteiro versionado o executava: as provas
# do SEGREDOS (03/10/2026) e do D442 ficaram em rascunho. Ate 07/10/2026 ele
# decidia ler o stdin por `[ ! -t 0 ]`, e chamado a mao fora de terminal -- o
# Bash de um agente, uma IDE -- o `read` esperava para sempre. Agora quem
# decide sao os dois argumentos que o git sempre passa, e chamado sem eles o
# hook diz no stderr que nao leu o stdin.
#
# COMO. Um repositorio de rascunho num diretorio temporario, com tres commits
# (c1, c2, c3), o hook copiado e o `scripts/conferir-segredos.sh` trocado por um
# duble que anota cada commit que recebe e recusa o que estiver em REPROVAR.
# Nenhum `git push`: o hook e chamado como o git o chama -- `origin <url>` e as
# linhas no stdin -- ou como alguem o chama a mao.
#
# O STDIN QUE NAO FECHA e um `sleep` por substituicao de processo, morto no fim
# do caso; o `timeout` separa "terminou" de "ficou preso no read" (124).
#
# Saida: 0 com os casos na saida esperada; 1 com algum fora dela; 2 quando nao
# consegue montar o rascunho.
#
# PARA VER O ENSAIO PEGAR UM DEFEITO, aponte-o para outra versao do hook:
#
#     PRE_PUSH_A_ENSAIAR=/caminho/do/hook bash scripts/ensaiar-pre-push.sh

set -u
cd "$(dirname "$0")/.."
RAIZ=$(pwd)
HOOK_ORIGEM="${PRE_PUSH_A_ENSAIAR:-$RAIZ/.githooks/pre-push}"
ZERO=0000000000000000000000000000000000000000

T=$(mktemp -d) || { echo "Nao consegui criar o diretorio temporario." >&2; exit 2; }
trap 'rm -rf "$T"' EXIT

R="$T/rascunho"
REGISTRO="$T/conferidos.txt"
export REGISTRO

nao_montei() { echo "NAO CONSEGUI MONTAR O RASCUNHO: $1" >&2; exit 2; }

mkdir -p "$R/.githooks" "$R/scripts" || nao_montei "os diretorios"
cp "$HOOK_ORIGEM" "$R/.githooks/pre-push" || nao_montei "a copia do hook"

# O DUBLE do portao de segredos. O hook o chama por
# `sh "$RAIZ/scripts/conferir-segredos.sh" <commit>`, com a raiz do rascunho.
printf '%s\n' \
    'printf "%s\n" "$1" >> "$REGISTRO"' \
    '[ "${REPROVAR:-}" = "$1" ] && exit 1' \
    'exit 0' > "$R/scripts/conferir-segredos.sh" || nao_montei "o duble"

# O GIT DO RASCUNHO, sem os hooks nem a assinatura de quem roda o ensaio.
G() {
    git -c user.name=ensaio -c user.email=ensaio@exemplo.invalid \
        -c commit.gpgsign=false -c core.hooksPath=/dev/null "$@"
}

cd "$R" || nao_montei "o diretorio do rascunho"
G init -q > /dev/null 2>&1 || nao_montei "o git init"
for i in 1 2 3; do
    echo "$i" > "arquivo$i.txt"
    { G add -A && G commit -q -m "c$i"; } > /dev/null 2>&1 || nao_montei "o commit c$i"
done
C1=$(git rev-parse HEAD~2) && C2=$(git rev-parse HEAD~1) && C3=$(git rev-parse HEAD) ||
    nao_montei "os tres commits"

URL="$T/remoto-que-nao-existe.git"
FAIXA="refs/heads/main $C3 refs/heads/main $C1"

# Roda o hook do jeito que o resto da linha diz, e guarda a saida, o stderr e
# os commits que o duble recebeu -- com os nomes c1, c2 e c3, em ordem.
roda() {
    : > "$REGISTRO"
    "$@" > "$T/saida.txt" 2> "$T/erro.txt"
    SAIDA=$?
    CONFERIDOS=$(sed -e "s/$C1/c1/" -e "s/$C2/c2/" -e "s/$C3/c3/" "$REGISTRO" | sort | tr '\n' ' ' | sed 's/ $//')
}

n=0
falhas=0
# $1 a descricao; $2 a saida esperada; $3 os commits conferidos ("*" nao confere);
# $4 se o aviso de stdin nao lido tem de aparecer (sim/nao).
espera() {
    local ok=1
    n=$((n+1))
    [ "$SAIDA" = "$2" ] || ok=""
    [ "$3" = "*" ] || [ "$CONFERIDOS" = "$3" ] || ok=""
    if grep -q 'o stdin nao e lido' "$T/erro.txt"; then
        [ "$4" = sim ] || ok=""
    else
        [ "$4" = nao ] || ok=""
    fi
    if [ -n "$ok" ]; then
        printf '  ok      %s\n' "$1"
    else
        printf '  FALHOU  %s\n' "$1"
        printf '          saida %s (esperada %s); conferidos [%s] (esperados [%s]); aviso esperado: %s\n' \
            "$SAIDA" "$2" "$CONFERIDOS" "$3" "$4"
        falhas=$((falhas+1))
    fi
}

echo ''
echo 'O PRE-PUSH (D442)'
echo '=============================================================================='

# A MAO, sem argumento, com um stdin que nao fecha -- o Bash de um agente.
exec 3< <(exec sleep 60 2> /dev/null)
SONO=$!
roda timeout 20 sh .githooks/pre-push <&3
exec 3<&-
kill "$SONO" 2> /dev/null
espera "a mao, sem argumento, com um stdin que nao fecha: termina e confere o HEAD" 0 "c3" sim

printf '%s\n' "$FAIXA" > "$T/linhas.txt"
roda sh .githooks/pre-push < "$T/linhas.txt"
espera "a mao, sem argumento, com a faixa num arquivo: confere so o HEAD, e avisa que nao leu" 0 "c3" sim

roda sh .githooks/pre-push origin "$URL" <<< "$FAIXA"
espera "como o git: origin <url> e a faixa c1..c3 -- confere c2 e c3" 0 "c2 c3" nao

roda sh .githooks/pre-push origin "$URL" <<< "refs/heads/main $C3 refs/heads/main $ZERO"
espera "como o git, ramo novo (sha remota zero): confere os tres" 0 "c1 c2 c3" nao

roda sh .githooks/pre-push origin "$URL" <<< "(delete) $ZERO refs/heads/some $C3"
espera "como o git, apagando um ramo (sha local zero): nada conferido" 0 "" nao

roda sh .githooks/pre-push origin "$URL" < /dev/null
espera "com os dois argumentos e o stdin vazio: confere o HEAD" 0 "c3" nao

REPROVAR=$C2 roda sh .githooks/pre-push origin "$URL" <<< "$FAIXA"
espera "o portao recusa o commit do MEIO da faixa: o push e bloqueado" 1 "*" nao

echo ''
echo "  $n casos, $falhas fora da saida esperada."
echo ''
[ "$falhas" -eq 0 ] || exit 1
exit 0
