#!/usr/bin/env bash
#
# Roda a suíte de contrato contra o banco certo, e devolve o ambiente como
# estava.
#
#     scripts/rodar-contrato.sh              # confere
#     scripts/rodar-contrato.sh --gravar     # regrava os snapshots
#     scripts/rodar-contrato.sh --montar     # recria o banco antes
#
# POR QUE ISTO EXISTE. A suíte de contrato precisa do cenário determinístico --
# `mysql.sql` mais `fixtures.sql` --, e o banco de desenvolvimento tem a cópia
# anonimizada da produção por cima. Apontada para o banco errado, ela falha em
# cascata com diferenças que não são regressão de contrato.
#
# Isso já custou caro duas vezes no mesmo dia, e uma delas levou a um débito
# escrito com o diagnóstico errado (D55). `ContratoTestCase` passou a recusar o
# banco errado; este roteiro faz o passo seguinte, que é não deixar acontecer.
#
# A dança que ele evita: salvar o local.php, trocar o dbname, limpar os proxies
# do Doctrine, rodar, e restaurar -- inclusive quando a suíte falha, que é
# justamente quando esquecer de restaurar dói mais.
#
# NAO RODE NADA JUNTO COM ELE. Enquanto este roteiro roda, o
# `config/autoload/local.php` -- que e compartilhado por tudo -- aponta para o
# banco de contrato. Uma suite de caracterizacao iniciada nessa janela le o
# banco errado, e ela quase nao percebe: passa 108 de 109 sobre 36 linhas.
#
# Aconteceu em 31/08/2026 e custou um diagnostico inteiro. A protecao ficou do
# outro lado, em `CaracterizacaoTestCase::exigirCopiaDaProducao`, porque la ela
# pega a causa independente de quem a criou -- inclusive uma execucao deste
# roteiro interrompida antes do `trap`.

set -euo pipefail

BANCO="franquiabv_contrato"
LOCAL="config/autoload/local.php"
BACKUP="$(mktemp)"
GRAVAR=""
MONTAR=""

for arg in "$@"; do
    case "$arg" in
        --gravar) GRAVAR="1" ;;
        --montar) MONTAR="1" ;;
        *) echo "argumento desconhecido: $arg" >&2; exit 2 ;;
    esac
done

if [ ! -f "$LOCAL" ]; then
    echo "não achei $LOCAL — rode a partir da raiz do projeto." >&2
    exit 2
fi

# ---------------------------------------------------------------------------
# ELE NAO PODE SER ENVENENADO PELA PROPRIA FALHA ANTERIOR.
#
# O `trap` abaixo restaura o `local.php` a partir de um backup feito AGORA. Se
# uma execucao anterior morreu de um jeito que o `trap` nao alcanca -- SIGPIPE
# de um `| head`, kill -9, o processo pai levando o filho junto --, o
# `local.php` fica apontando para o banco de contrato. Na vez seguinte, o backup
# copia esse estado e a "restauracao" devolve o banco errado, com a mensagem
# tranquilizadora "ambiente restaurado".
#
# Aconteceu em 03/09/2026. A suite de caracterizacao teria rodado contra o banco
# de contrato -- 35 linhas de fixture no lugar da copia de producao. Ela tem
# guarda propria (`exigirCopiaDaProducao`) e teria acusado, mas contar com a
# guarda do vizinho e como nao ter guarda.
# ---------------------------------------------------------------------------
if grep -q "'dbname' => '${BANCO}'" "$LOCAL"; then
    echo "O local.php JA APONTA para ${BANCO}." >&2
    echo >&2
    echo "  Isso quer dizer que uma execucao anterior nao restaurou o ambiente." >&2
    echo "  Comecar assim gravaria o estado errado no backup, e a restauracao" >&2
    echo "  devolveria o banco de contrato dizendo que restaurou." >&2
    echo >&2
    echo "  Aponte o local.php de volta para o banco de desenvolvimento antes:" >&2
    echo >&2
    echo "      sed -i \"s/'dbname' => '${BANCO}'/'dbname' => 'franquiabv_dev'/\" $LOCAL" >&2
    exit 2
fi

cp "$LOCAL" "$BACKUP"

# O restore roda aconteça o que acontecer: suíte vermelha, Ctrl-C, erro de
# docker. Deixar o local.php apontando para o banco de contrato depois de uma
# execução interrompida faria a suíte de caracterização rodar no lugar errado
# na vez seguinte -- e o estrago seria silencioso.
restaurar() {
    cp "$BACKUP" "$LOCAL"
    rm -f "$BACKUP"
    docker compose exec -T app sh -c 'rm -rf data/DoctrineORMModule/Proxy/* data/cache/*; true' >/dev/null 2>&1 || true
    echo
    echo "ambiente restaurado: $(grep -m1 dbname "$LOCAL" | tr -d ' ')"

    # A RESTAURACAO CONFERE A SI MESMA. Dizer "restaurado" e devolver o banco de
    # contrato foi exatamente o que aconteceu; a mensagem sozinha nao vale nada.
    if grep -q "'dbname' => '${BANCO}'" "$LOCAL"; then
        echo >&2
        echo "  ATENCAO: o local.php CONTINUA apontando para ${BANCO}." >&2
        echo "  Corrija antes de rodar qualquer outra suite." >&2
    fi
}
trap restaurar EXIT INT TERM

if [ -n "$MONTAR" ]; then
    bash scripts/montar-banco-contrato.sh "$BANCO"
fi

# ---------------------------------------------------------------------------
# O BANCO DE CONTRATO ENVELHECE EM SILENCIO, e o sintoma mente.
#
# Ele e montado uma vez, a partir de `mysql.sql`. Quem acrescenta uma coluna
# ali -- e as entidades passam a mapea-la -- deixa este banco para tras: toda
# consulta que toque a tabela vira HTTP 500, e a suite reporta "o contrato do
# endpoint mudou". Nao mudou. O banco e que ficou velho.
#
# Aconteceu em 03/09/2026, com as duas colunas do D86 (`vendas.autorizado_por`
# e `autorizado_em`). Tres testes ficaram vermelhos e a mensagem apontava para
# o lugar errado -- ate a suite de contrato passar a rodar contra um banco sem
# as colunas que o codigo ja exigia.
#
# Esta conferencia compara as colunas de `mysql.sql` com as que o banco tem, e
# manda montar de novo em vez de deixar a suite acusar a coisa errada. Ela e
# barata: uma consulta ao catalogo.
# ---------------------------------------------------------------------------
# A comparacao e EXATA, por par tabela/coluna -- e nao por contagem.
#
# A primeira versao contava colunas dos dois lados. Os numeros nao batiam por
# um (887 no arquivo, 888 no banco), e com isso UMA coluna faltando passaria
# despercebida: 887 nao e menor que 887. Contagem aproximada num portao e o
# mesmo que portao nenhum.
# SEM `trap` NOVO AQUI, e isto e importante: um segundo `trap ... EXIT`
# SUBSTITUI o `trap restaurar` instalado acima, e o local.php ficaria apontando
# para o banco de contrato depois que a suite terminasse. E o estrago silencioso
# que este roteiro existe para evitar -- eu quase o reintroduzi ao escrever esta
# conferencia. Os temporarios saem a mao, logo abaixo.
ARQ_COLUNAS=$(mktemp)
BD_COLUNAS=$(mktemp)

awk '
    /^CREATE TABLE / {
        tabela = $3
        gsub(/`/, "", tabela)
        next
    }
    /^  `[a-z_]+` / && tabela != "" {
        coluna = $1
        gsub(/`/, "", coluna)
        print tabela "." coluna
    }
    /^\) ENGINE/ { tabela = "" }
' mysql.sql | sort -u > "$ARQ_COLUNAS"

docker compose exec -T mysql sh -c     "mysql -uroot -proot -N -e \"SELECT CONCAT(table_name, '.', column_name) FROM information_schema.columns WHERE table_schema='${BANCO}'\"" 2>/dev/null     | grep -v "Using a password" | tr -d '' | sort -u > "$BD_COLUNAS"

FALTANDO=$(comm -23 "$ARQ_COLUNAS" "$BD_COLUNAS")
VAZIO_BD=$([ -s "$BD_COLUNAS" ] && echo nao || echo sim)
rm -f "$ARQ_COLUNAS" "$BD_COLUNAS"

if [ "$VAZIO_BD" = "nao" ] && [ -n "$FALTANDO" ]; then
    echo "O BANCO DE CONTRATO ESTA DEFASADO." >&2
    echo >&2
    echo "  Colunas que o mysql.sql tem e o banco ${BANCO} nao:" >&2
    printf '%s
' "$FALTANDO" | sed 's/^/      /' >&2
    echo >&2
    echo "  Rodar assim daria HTTP 500, e a suite diria 'o contrato do endpoint" >&2
    echo "  mudou' -- que seria mentira. Monte o banco de novo:" >&2
    echo >&2
    echo "      sh scripts/rodar-contrato.sh --montar" >&2
    exit 2
fi

# E O CENARIO, que envelhece do mesmo jeito e nao aparecia em lugar nenhum.
#
# A conferencia acima cuida das COLUNAS. As LINHAS ficavam de fora: fixture
# nova, banco montado ontem, e a suite verde sobre um cenario que ja mudou. Em
# 06/09/2026 isso escondeu quatro linhas novas por uma tarde, e o PostgreSQL --
# que remonta o banco a cada execucao -- acusou uma diferenca que o MySQL nao
# via. Dois bancos discordando por causa da idade de um deles e o tipo de
# fantasma que come um dia de investigacao.
# A MARCA VEM DO PROPRIO ARQUIVO, e nao de um `sha1sum` calculado aqui.
#
# Desde 07/09/2026 o `fixtures.sql` termina declarando o sha1 do que esta acima,
# e QUEM CARREGA O ARQUIVO GRAVA A MARCA sem saber que grava -- os sete lugares
# que carregam fixtures passaram a marcar o banco de graca. Calcular de novo
# aqui daria outro numero (a marca nao entra no proprio calculo) e este portao
# acusaria todo mundo.
#
# `scripts/marcar-fixtures.php --conferir`, no `conferir-tudo.sh`, e quem
# garante que a marca declarada descreve mesmo o arquivo.
SHA_ARQUIVO=$(php scripts/marcar-fixtures.php --sha)
SHA_BANCO=$(docker compose exec -T mysql sh -c "mysql -uroot -proot -N -e \"SELECT sha FROM ${BANCO}._fixtures_versao LIMIT 1\"" 2>/dev/null | grep -v "Using a password" | head -1 | tr -d ' ')

if [ "$SHA_ARQUIVO" != "$SHA_BANCO" ]; then
    echo "O CENARIO DO BANCO DE CONTRATO ESTA DEFASADO." >&2
    echo >&2
    echo "  fixtures.sql: ${SHA_ARQUIVO}" >&2
    echo "  ${BANCO}: ${SHA_BANCO:-nenhuma marca gravada}" >&2
    echo >&2
    echo "  A suite rodaria contra o cenario de antes, e passaria -- e verde por" >&2
    echo "  banco antigo nao aparece em relatorio nenhum. Monte o banco de novo:" >&2
    echo >&2
    echo "      sh scripts/rodar-contrato.sh --montar" >&2
    exit 2
fi

sed -i "s/'dbname' => '[a-z_]*'/'dbname' => '${BANCO}'/" "$LOCAL"
docker compose exec -T app sh -c 'rm -rf data/DoctrineORMModule/Proxy/* data/cache/*; true'

# O OPCACHE MEDE O PASSADO: o php-fpm ainda serve o `local.php` de antes por
# ate ~3 s depois da troca, e o primeiro arquivo da suite ia ao banco velho
# (o vermelho do aparelho, nas fatias 76d e 81). Ver `esperar-o-opcache.sh`.
sh scripts/esperar-o-opcache.sh

# A senha do usuario de fixture, para a suite saber logar (D59).
#
# DEPOIS DO `sed`, e nao antes: o roteiro acha o banco pelo `local.php`, e antes
# desta linha ele ainda aponta para o de desenvolvimento. Ele tem guarda propria
# e recusaria rodar la, mas contar com a guarda do outro nao e ordem.
#
# O CI tem o passo equivalente no `.github/workflows/ci.yml`. Os dois precisam
# existir: la o banco nasce de `mysql.sql` mais `fixtures.sql`, sem passar por
# este roteiro.
docker compose exec -T app php scripts/definir-senha-contrato.php

echo "rodando a suíte de contrato contra ${BANCO}..."
echo

if [ -n "$GRAVAR" ]; then
    echo "  ATENÇÃO: modo de GRAVAÇÃO. Os snapshots serão reescritos."
    echo "  Confira o diff antes de commitar: um contrato alterado sem intenção"
    echo "  quebra aplicativo instalado que ninguém pode atualizar (D5)."
    echo
    docker compose exec -T -e ATUALIZAR_SNAPSHOTS=1 app phpunit --testsuite contrato
else
    docker compose exec -T app phpunit --testsuite contrato
fi
