#!/bin/sh
#
# O passo 7 do corte troca so o bloco `doctrine` do local.php de producao? (D329)
#
#     sh scripts/ensaiar-troca-do-doctrine.sh
#     sh scripts/ensaiar-troca-do-doctrine.sh <copia do scripts/trocar-doctrine-do-local-php.php>
#
# Sai com 0 quando todo caso abaixo da o esperado; 1 quando algum nao da, e diz
# qual; 2 quando nao consegue medir (sem `php`, `mktemp` ou `cmp`). O argumento
# aponta o ensaio para uma COPIA do roteiro -- e como se falsifica sem tocar no
# de verdade.
#
# ---------------------------------------------------------------------------
# POR QUE ISTO EXISTE (08/11/2026, a fatia 128; o D329)
#
# O passo 7 de `docs/procedimento-de-corte.md` mandava copiar o
# `local.postgres.php.dist` -- o modelo de DESENVOLVIMENTO -- por cima do
# `local.php` de producao: o rastro de pilha ligado, o e-mail no Mailpit, e sem
# OneSignal, Yungas, Asaas nem `cron.token`. Agora o passo parte do
# `local.mysql.php.guardado` e troca so o bloco `doctrine`, pelo
# `scripts/trocar-doctrine-do-local-php.php`, e o `verificar-local-php.php` vem
# antes do passo 8.
#
# COMO: tudo num diretorio temporario, sobre um local.php INVENTADO no formato do
# de producao (comentarios, o MySQL com `driverOptions`, as chaves de
# integracao com valores de mentira). O `config/autoload/local.php` daqui nunca
# e lido nem escrito. Os casos:
#
#   a troca, verde             sai 0; o arquivo novo, avaliado, aponta para o
#                              PostgreSQL com bvip_app; o texto antes e depois do
#                              bloco e o do guardado, byte a byte; o guardado nao
#                              muda; a senha nao aparece na saida; e o
#                              `verificar-local-php.php` sai 0 com ele
#   o passo antigo (o cp do    o `verificar-local-php.php` sai 1 (o rastro de
#   dist), como contraste      pilha): e o defeito que o D329 registrou
#   sem SENHA_APP              sai 2, e nada e escrito
#   o guardado ja do           sai 1, e nada e escrito
#   PostgreSQL
#   o doctrine com outra chave sai 1 com o nome dela, e nada e escrito
#   o doctrine duas vezes      sai 1, e nada e escrito
#   o destino e o guardado     sai 1, e o guardado nao muda
#   o destino que ja existe    a permissao dele fica
#   (a correcao da 128, as tres guardas sem caso e o temporario:)
#   o doctrine com outra       sai 1 com o nome dela, e nada e escrito (a
#   conexao                    conferencia avaliada nao a pega: o bloco novo
#                              e o pedido, e a outra conexao sumiria calada)
#   o orm_default com uma      sai 1 com o nome dela, e nada e escrito
#   chave fora das tres
#   a troca que muda outra     sai 1 com o nome da chave (um `__LINE__` depois
#   chave                      do bloco, que muda com o tamanho dele)
#   o destino novo             nasce com a permissao e o grupo do guardado
#   a avaliacao que morre      o guardado que declara uma funcao no topo morre
#                              na segunda avaliacao ("Cannot redeclare"): nada
#                              e escrito, e o temporario com a senha nao fica
#
# O QUE ELE NAO AFIRMA: que o local.php de producao tem esta forma. Ninguem o
# leu daqui (ele so existe no servidor); se tiver outra chave dentro do bloco
# `doctrine`, o roteiro recusa com o nome dela, e o operador decide na janela.

set -u

cd "$(dirname "$0")/.." || exit 2
RAIZ=$(pwd)

nao_medi() { echo "NAO CONSEGUI MEDIR: $1" >&2; exit 2; }

ROTEIRO="${1:-scripts/trocar-doctrine-do-local-php.php}"
[ -r "$ROTEIRO" ] || nao_medi "nao li $ROTEIRO"
command -v php > /dev/null 2>&1 || nao_medi "sem php."
command -v cmp > /dev/null 2>&1 || nao_medi "sem cmp."

T=$(mktemp -d 2>/dev/null) || nao_medi "mktemp -d falhou."
trap 'rm -rf "$T"' EXIT INT TERM

# A senha de mentira do bvip_app, numa constante (o pre-push recusa senha escrita).
SENHA_DO_ENSAIO='pg-de-mentira-128'

# ---------------------------------------------------------------------------
# O local.php inventado, em tres partes: antes do bloco, o bloco, e depois.
# ---------------------------------------------------------------------------

cat > "$T/antes.txt" <<'FIM'
<?php

/**
 * O local.php de producao, INVENTADO para o ensaio do D329.
 */

return [
    // O e-mail de verdade.
    'mail' => [
        'name' => 'smtp.exemplo',
        'host' => 'smtp.exemplo',
        'port' => '587',
    ],

FIM
# O recuo da chave fica no fim do `antes`: o bloco comeca na aspa dela.
printf '    ' >> "$T/antes.txt"
printf "'doctrine' => [\n        'connection' => [\n            'orm_default' => [\n                'params' => [\n                    'host' => 'db-my',\n                    'port' => '3306',\n                    'dbname' => 'prod_x',\n                    'user' => 'app',\n                    'password' => 'abc',\n                    'driverOptions' => [1002 => 'SET NAMES utf8mb4'],\n                ],\n            ],\n        ],\n    ]" > "$T/bloco.txt"
cat > "$T/depois.txt" <<'FIM'
,

    'onesignal' => ['app' => ['app_id' => 'os1']],
    'asaas' => ['api_key' => 'as1'],
    'cron' => ['token' => 'tk1'],
    'yungas' => ['apikey' => 'yg1', 'email' => 'y@x', 'password' => 'yg2'],
    'whatsapp' => ['central' => ['instancia' => 'wc1', 'api_key' => 'wc2']],
];
FIM
# O bloco com uma chave a mais, para o caso que recusa.
printf "'doctrine' => [\n        'configuration' => ['orm_default' => ['generate_proxies' => false]],\n" > "$T/bloco-com-config.txt"
tail -n +2 "$T/bloco.txt" >> "$T/bloco-com-config.txt"
{ cat "$T/antes.txt"; cat "$T/bloco.txt"; cat "$T/depois.txt"; } > "$T/guardado.php"
cp "$T/guardado.php" "$T/guardado.original.php"

php -l "$T/guardado.php" > /dev/null 2>&1 || nao_medi "o local.php inventado nao e PHP valido."

# ---------------------------------------------------------------------------
# As conferencias.
# ---------------------------------------------------------------------------

falhas=0
casos=0
caso=''
falhou() { echo "  - $caso: $1"; falhas=$((falhas + 1)); }
novo_caso() { casos=$((casos + 1)); caso=$1; rm -f "$T/destino.php" "$T/saida.txt"; }
saiu() { [ "$codigo" = "$1" ] || falhou "saiu $codigo, e devia sair $1 ($(tail -n 1 "$T/saida.txt"))"; }
nada_escrito() { [ ! -e "$T/destino.php" ] || falhou 'nada devia ser escrito, e o destino existe'; }
guardado_igual() { cmp -s "$T/guardado.original.php" "$1" || falhou 'o guardado mudou'; }

# trocar <guardado> [VAR=valor ...]: o roteiro, com o destino no temporario.
trocar() {
    g=$1
    shift
    env "$@" php "$ROTEIRO" "$g" "$T/destino.php" --host=pg-host --banco=banco_pg > "$T/saida.txt" 2>&1
    codigo=$?
}

# 1. A troca, verde.
novo_caso 'a troca, verde'
trocar "$T/guardado.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 0
[ "$(php scripts/conexao-do-local-php.php "$T/destino.php" 2>&1)" = 'pg-host|banco_pg|bvip_app' ] ||
    falhou "o arquivo novo, avaliado, aponta para: $(php scripts/conexao-do-local-php.php "$T/destino.php" 2>&1)"
php -r '
    $c = require $argv[1];
    $o = $c["doctrine"]["connection"]["orm_default"];
    exit(($o["driverClass"] === "Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver"
        && $o["params"]["port"] === "5432"
        && $o["params"]["password"] === $argv[2]
        && !isset($o["driverOptions"], $o["params"]["driverOptions"])) ? 0 : 1);
' "$T/destino.php" "$SENHA_DO_ENSAIO" || falhou 'o driver, a porta e a senha do PostgreSQL, sem o driverOptions do MySQL'
tamanho_antes=$(wc -c < "$T/antes.txt" | tr -d ' ')
tamanho_depois=$(wc -c < "$T/depois.txt" | tr -d ' ')
head -c "$tamanho_antes" "$T/destino.php" | cmp -s - "$T/antes.txt" || falhou 'o texto antes do bloco mudou'
tail -c "$tamanho_depois" "$T/destino.php" | cmp -s - "$T/depois.txt" || falhou 'o texto depois do bloco mudou'
guardado_igual "$T/guardado.php"
! grep -qF "$SENHA_DO_ENSAIO" "$T/saida.txt" || falhou 'a senha aparece na saida'
php scripts/verificar-local-php.php "$T/destino.php" --sem-conexao > "$T/verificar.txt" 2>&1 ||
    falhou "o verificar-local-php.php recusa o arquivo novo ($(tail -n 1 "$T/verificar.txt"))"

# 2. O passo antigo, como contraste: o dist por cima do de producao.
novo_caso 'o passo antigo, o cp do dist'
cp "$RAIZ/config/autoload/local.postgres.php.dist" "$T/destino.php"
codigo=0
php scripts/verificar-local-php.php "$T/destino.php" --sem-conexao > "$T/saida.txt" 2>&1 || codigo=$?
saiu 1

# 3. Sem SENHA_APP.
novo_caso 'sem SENHA_APP'
trocar "$T/guardado.php" SENHA_APP=
saiu 2
nada_escrito

# 4. O guardado errado: ja o do PostgreSQL.
novo_caso 'o guardado ja do PostgreSQL'
cp "$RAIZ/config/autoload/local.postgres.php.dist" "$T/dist.php"
trocar "$T/dist.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 1
nada_escrito

# 5. O doctrine com uma chave que o bloco novo nao levaria.
novo_caso 'o doctrine com outra chave'
{ cat "$T/antes.txt"; cat "$T/bloco-com-config.txt"; cat "$T/depois.txt"; } > "$T/com-config.php"
grep -q "'configuration'" "$T/com-config.php" || nao_medi 'o caso 5 nao montou o guardado com a chave a mais.'
trocar "$T/com-config.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 1
grep -qF 'doctrine.configuration' "$T/saida.txt" || falhou 'a recusa diz o nome da chave'
nada_escrito

# 6. O doctrine duas vezes no arranjo de cima (o PHP fica com o ultimo).
novo_caso 'o doctrine duas vezes'
{ cat "$T/antes.txt"; cat "$T/bloco.txt"; printf ',\n    '; cat "$T/bloco.txt"; cat "$T/depois.txt"; } > "$T/duas-vezes.php"
trocar "$T/duas-vezes.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 1
grep -qF "aparece 2 vez(es)" "$T/saida.txt" || falhou 'a recusa e a da contagem do bloco (a conferencia avaliada e a segunda guarda)'
nada_escrito

# 7. O destino e o proprio guardado.
novo_caso 'o destino e o guardado'
cp "$T/guardado.php" "$T/destino.php"
codigo=0
SENHA_APP="$SENHA_DO_ENSAIO" php "$ROTEIRO" "$T/destino.php" "$T/destino.php" --host=pg-host --banco=banco_pg > "$T/saida.txt" 2>&1 || codigo=$?
saiu 1
guardado_igual "$T/destino.php"

# 8. O destino que ja existe guarda a permissao dele.
novo_caso 'o destino que ja existe'
: > "$T/destino.php"
chmod 600 "$T/destino.php"
trocar "$T/guardado.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 0
case "$(ls -l "$T/destino.php")" in
    -rw-------*) ;;
    *) falhou "a permissao do destino mudou: $(ls -l "$T/destino.php" | cut -c1-10)" ;;
esac

# 9. O doctrine com outra conexao, que o bloco novo nao levaria.
novo_caso 'o doctrine com outra conexao'
{ cat "$T/antes.txt"; printf "'doctrine' => [\n        'connection' => [\n            'orm_outro' => ['params' => ['host' => 'db-outro']],\n"; tail -n +3 "$T/bloco.txt"; cat "$T/depois.txt"; } > "$T/outra-conexao.php"
php -r '$c = require $argv[1]; exit(isset($c["doctrine"]["connection"]["orm_outro"], $c["doctrine"]["connection"]["orm_default"]["params"]) ? 0 : 1);' "$T/outra-conexao.php" ||
    nao_medi 'o caso 9 nao montou o guardado com as duas conexoes.'
trocar "$T/outra-conexao.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 1
grep -qF 'doctrine.connection.orm_outro' "$T/saida.txt" || falhou 'a recusa diz o nome da conexao'
nada_escrito

# 10. O orm_default com uma chave fora de params, driverClass e driverOptions.
novo_caso 'o orm_default com uma chave a mais'
{ cat "$T/antes.txt"; printf "'doctrine' => [\n        'connection' => [\n            'orm_default' => [\n                'wrapperClass' => 'Exemplo\\\\Conexao',\n"; tail -n +4 "$T/bloco.txt"; cat "$T/depois.txt"; } > "$T/chave-a-mais.php"
php -r '$c = require $argv[1]; exit(isset($c["doctrine"]["connection"]["orm_default"]["wrapperClass"], $c["doctrine"]["connection"]["orm_default"]["params"]) ? 0 : 1);' "$T/chave-a-mais.php" ||
    nao_medi 'o caso 10 nao montou o guardado com a chave a mais.'
trocar "$T/chave-a-mais.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 1
grep -qF 'doctrine.connection.orm_default.wrapperClass' "$T/saida.txt" || falhou 'a recusa diz o nome da chave'
nada_escrito

# 11. A troca que muda uma chave fora do doctrine: o `__LINE__` depois do bloco
# muda com o numero de linhas dele.
novo_caso 'a troca que muda outra chave'
{ cat "$T/antes.txt"; cat "$T/bloco.txt"; printf ",\n    'linha_do_ensaio' => __LINE__"; cat "$T/depois.txt"; } > "$T/com-linha.php"
php -l "$T/com-linha.php" > /dev/null 2>&1 || nao_medi 'o caso 11 nao montou PHP valido.'
trocar "$T/com-linha.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 1
grep -qF 'difere do guardado em: linha_do_ensaio' "$T/saida.txt" || falhou "a recusa diz o nome da chave ($(tail -n 1 "$T/saida.txt"))"
nada_escrito

# 12. O destino novo copia a permissao e o grupo do guardado.
novo_caso 'o destino novo'
cp "$T/guardado.php" "$T/guardado-640.php"
chmod 640 "$T/guardado-640.php"
modo() { php -r 'printf("%o", fileperms($argv[1]) & 0777);' "$1"; }
grupo() { php -r 'echo filegroup($argv[1]);' "$1"; }
# Um grupo do usuario que nao e o que o arquivo novo ganharia sozinho (o do
# processo, no Linux; o do diretorio, no macOS) -- senao o chgrp nao se mede.
outro_grupo=''
for g in $(id -G); do
    if [ "$g" != "$(id -g)" ] && [ "$g" != "$(grupo "$T")" ]; then
        outro_grupo=$g
        break
    fi
done
[ -z "$outro_grupo" ] || chgrp "$outro_grupo" "$T/guardado-640.php" 2>/dev/null || outro_grupo=''
trocar "$T/guardado-640.php" SENHA_APP="$SENHA_DO_ENSAIO"
saiu 0
[ "$(modo "$T/destino.php")" = 640 ] || falhou "o destino novo nasceu com $(modo "$T/destino.php"), e o guardado tem 640"
if [ -n "$outro_grupo" ]; then
    [ "$(grupo "$T/destino.php")" = "$outro_grupo" ] || falhou "o destino novo ficou no grupo $(grupo "$T/destino.php"), e o guardado esta no $outro_grupo"
else
    echo "  (o caso 12 nao mede o grupo: este usuario nao tem um grupo alem do padrao)"
fi

# 13. A avaliacao do arquivo novo morre: o temporario com a senha nao fica.
novo_caso 'a avaliacao que morre'
{ printf '<?php\n\nfunction ensaio_d329_do_topo(): string\n{\n    return %s;\n}\n' "'x'"; tail -n +2 "$T/antes.txt"; cat "$T/bloco.txt"; cat "$T/depois.txt"; } > "$T/com-funcao.php"
php -l "$T/com-funcao.php" > /dev/null 2>&1 || nao_medi 'o caso 13 nao montou PHP valido.'
mkdir -p "$T/tmp-do-roteiro"
trocar "$T/com-funcao.php" SENHA_APP="$SENHA_DO_ENSAIO" TMPDIR="$T/tmp-do-roteiro"
[ "$codigo" != 0 ] || falhou 'saiu 0, e devia morrer na segunda avaliacao'
grep -qF 'Cannot redeclare' "$T/saida.txt" || nao_medi "o caso 13 nao morreu como se esperava ($(tail -n 1 "$T/saida.txt"))."
nada_escrito
[ -z "$(ls -A "$T/tmp-do-roteiro")" ] || falhou "o temporario ficou: $(ls -A "$T/tmp-do-roteiro" | tr '\n' ' ')"

# ---------------------------------------------------------------------------

if [ "$falhas" -gt 0 ]; then
    echo "O ENSAIO DA TROCA DO DOCTRINE FALHOU: $falhas conferencia(s) em $casos casos."
    exit 1
fi

echo "A troca do doctrine do passo 7: $casos casos, todos como deviam."
exit 0
