#!/bin/sh
#
# Ensaio geral do corte: roda a sequência inteira, cronometra cada fase e grava
# um registro comparável.
#
#   SENHA_APP='…' sh scripts/ensaiar-corte.sh primeira
#   SENHA_APP='…' sh scripts/ensaiar-corte.sh segunda
#   sh scripts/comparar-ensaios.sh ensaios/primeira.txt ensaios/segunda.txt
#
# POR QUE UM ROTEIRO E NÃO UMA LISTA DE COMANDOS. Duas execuções só podem ser
# comparadas se forem medidas do mesmo jeito. Cronometrar à mão produz números
# que não se comparam, e a comparação é o ponto do ensaio — uma execução
# sozinha não diz se o tempo é o tempo ou foi sorte.
#
# O QUE ELE NÃO FAZ: não decide nada. Se um portão falhar, ele para e diz onde.
#
# ATENÇÃO: apaga e recarrega o banco PostgreSQL de destino. Nunca aponte para
# produção. A guarda abaixo recusa nomes de banco que contenham "prod".
set -e


# ---------------------------------------------------------------------------
# A REDE PARA QUANDO ESTE ROTEIRO MORRER NO MEIO
#
# `apontar_postgres` guarda o local.php antes de sobrescreve-lo, e a fase
# `volta_atras` o restaura no fim. Entre as duas, o local.php aponta para o
# PostgreSQL -- e se o roteiro for interrompido nesse intervalo (Ctrl-C, kill,
# falha de uma fase com `set -e`), ele fica assim.
#
# A guarda do comeco do roteiro (`local_php_e_o_da_origem`, repetida em
# `apontar_postgres`) PEGA isso na proxima execucao, e falha alto -- desde
# 11/10/2026 tambem quando o que ficou e o local.php de um clone. O
# que ela nao faz e desfazer: no intervalo, qualquer outro comando que alguem
# rode vai para o banco errado sem aviso. Aconteceu em 30/08/2026, com uma
# sequencia escrita a mao que fazia o mesmo.
#
# O trap fecha o intervalo. Ele nao substitui a guarda -- as duas cobrem coisas
# diferentes: o trap desfaz o que este processo fez; a guarda protege contra o
# que um processo ANTERIOR deixou.
#
# SO O QUE ESTE PROCESSO FEZ, e pelo arquivo, nao pelo host (a verificacao do
# D378, 11/10/2026). O trap so age depois que a fase `apontar` trocou o
# local.php (`MEXI_NO_LOCAL_PHP`), e desfaz tudo o que nao seja o guardado, byte
# a byte: o do PostgreSQL, o do clone dele e o do clone do MySQL -- que tem o
# host `mysql`, como o da origem, e que a regra antiga, pelo host, dava por
# restaurado. E antes da `apontar` ele nao escreve: o guardado que estiver em
# `ensaios/` e de uma execucao ANTERIOR, talvez velho, e uma recusa do comeco
# (o nome do banco, o local.php de outra execucao) o copiava por cima.
restaurar_local_php() {
    [ -n "${MEXI_NO_LOCAL_PHP:-}" ] || return 0
    [ -f ensaios/local.mysql.guardado ] || return 0

    # Ja e o guardado: a volta_atras o devolveu, ou a troca nem chegou a valer.
    cmp -s ensaios/local.mysql.guardado config/autoload/local.php && return 0

    cp ensaios/local.mysql.guardado config/autoload/local.php
    echo >&2
    echo "AVISO: o ensaio terminou fora de hora, e o local.php foi restaurado" >&2
    echo "para o MySQL a partir de ensaios/local.mysql.guardado." >&2
}

trap restaurar_local_php EXIT INT TERM

ROTULO="${1:-}"

if [ -z "$ROTULO" ]; then
    echo "uso: SENHA_APP=… sh scripts/ensaiar-corte.sh <rotulo>" >&2
    echo >&2
    echo "O rótulo nomeia o registro em ensaios/. Use 'primeira' e 'segunda'." >&2
    exit 1
fi

if [ -z "$SENHA_APP" ]; then
    echo "ABORTADO: defina SENHA_APP." >&2
    exit 1
fi

PROJETO="${PROJETO:-franquiabv}"
BANCO_MYSQL="${BANCO_MYSQL:-franquiabv_dev}"
BANCO_PG="${BANCO_PG:-franquiabv_dev}"
USUARIO_PG="${USUARIO_PG:-franquiabv}"

# OS CLONES DA SUITE (D378; a resposta 7 do dono, 06/10/2026: a suite sai da
# janela, e no ensaio ela roda num clone carregado, sobre a copia). A suite de
# caracterizacao exige as fixtures, e a fase `conferencia` recusa a faixa delas
# nos dois bancos: elas vao so a uma copia de cada banco, feita depois da carga,
# e o ensaio apaga as duas no fim.
#
# O NOME DO CLONE NAO VEM DO AMBIENTE (a verificacao de 11/10/2026): o ensaio
# apaga e refaz o clone por nome, e um `CLONE_MYSQL=franquiabv_dev` de quem
# ensaia sobre outra copia passava pelas guardas e apagava o banco do
# desenvolvimento -- e o mesmo com o do contrato. Ele sai da origem, com
# `_suite` no fim, e por isso nunca e o nome dela.
CLONE_PG="${BANCO_PG}_suite"
CLONE_MYSQL="${BANCO_MYSQL}_suite"

case "$BANCO_PG$BANCO_MYSQL" in
    *prod*)
        echo "ABORTADO: o nome do banco contém 'prod'. Este roteiro APAGA o destino." >&2
        exit 1
        ;;
esac

# O CLONE E APAGADO E REFEITO a cada execucao, e o nome vai cru no SQL e no
# `grep` do local.php: so letras minusculas, digitos e sublinhado.
case "$BANCO_PG$BANCO_MYSQL" in
    *[!a-z0-9_]*)
        echo "ABORTADO: o nome do banco so pode ter letras minusculas, digitos e _." >&2
        exit 1
        ;;
esac

# O LOCAL.PHP TEM DE SER O DA ORIGEM, antes de qualquer fase (a verificacao do
# D378, 11/10/2026): o MySQL, com o banco de BANCO_MYSQL. E ele que a fase
# `apontar` guarda e a `volta_atras` devolve, e o conferidor da vigencia da volta
# atras le o banco dele. A guarda antiga, so pelo host e so no `apontar`,
# deixava passar o local.php do clone do MySQL (o host e o mesmo) que uma
# execucao morta a forca no meio da suite deixa: ele ia por cima do guardado
# bom, a volta atras media o clone, e o original se perdia. E ela vem antes da
# `limpeza` e do `passo3`, que escrevem na copia -- e o `passo3` so se mede na
# primeira execucao sobre ela.
#
# A CONEXAO E LIDA PELO PHP DO CONTEINER, e nao pelo texto (D469, desde
# 08/11/2026). A guarda procurava `'host' => 'mysql'` e `'dbname' => '<banco>'`
# escritos, e o local.php do laboratorio do BVIP e o do
# `docker/config/local.php.docker`, que le os dois do ambiente (`getenv`): o
# ensaio de la parava aqui, antes de comecar. O `scripts/conexao-do-local-php.php`
# avalia o arquivo no conteiner da aplicacao -- o ambiente que a suite e o site
# enxergam -- e devolve `<host>|<dbname>|<user>`, sem a senha. O conteiner fora
# do ar devolve vazio, e a guarda recusa.
conexao_do_local_php() {
    docker compose -p "$PROJETO" exec -T app php scripts/conexao-do-local-php.php "${1:-config/autoload/local.php}" < /dev/null 2>/dev/null
}

local_php_e_o_da_origem() {
    case "$(conexao_do_local_php)" in
        "mysql|$BANCO_MYSQL|"?*) return 0 ;;
    esac
    return 1
}

if ! local_php_e_o_da_origem; then
    echo "ABORTADO: config/autoload/local.php nao aponta para o MySQL de $BANCO_MYSQL." >&2
    echo "Uma execucao anterior parou no meio (o original pode estar em" >&2
    echo "ensaios/local.mysql.guardado), ou BANCO_MYSQL nao e o banco do local.php," >&2
    echo "ou o conteiner app de $PROJETO nao respondeu. O que o PHP de la le dele:" >&2
    echo "  $(conexao_do_local_php || true)" >&2
    exit 1
fi

mkdir -p ensaios
REGISTRO="ensaios/${ROTULO}.txt"
: > "$REGISTRO"

anotar() { printf '%-22s %s\n' "$1" "$2" >> "$REGISTRO"; }
secao()  { printf '\n== %s ==\n' "$1" >> "$REGISTRO"; echo; echo "== $1 =="; }

pg()  { docker compose -p "$PROJETO" exec -T postgres psql -U "$USUARIO_PG" -d "$BANCO_PG" -tA -c "$1" 2>&1 || true; }
my()  { docker compose -p "$PROJETO" exec -T mysql mysql -uroot -proot --batch --skip-column-names --default-character-set=utf8mb4 "$BANCO_MYSQL" -e "$1" 2>/dev/null || true; }

# ---------------------------------------------------------------------------
# O ambiente. Sem isto os tempos não significam nada: 533 segundos em 24
# núcleos e 533 em 4 são resultados diferentes com o mesmo número.
# ---------------------------------------------------------------------------
secao "ambiente"

anotar "rotulo"           "$ROTULO"
anotar "host_cpus"        "$(docker info --format '{{.NCPU}}' 2>/dev/null || echo '?')"

LIMITE_CPU=$(docker inspect "${PROJETO}-postgres-1" --format '{{.HostConfig.NanoCpus}}' 2>/dev/null || echo 0)
LIMITE_MEM=$(docker inspect "${PROJETO}-postgres-1" --format '{{.HostConfig.Memory}}' 2>/dev/null || echo 0)

# Um ensaio sem limite de contêiner mede a máquina do laboratório, não a de
# produção. O registro diz qual foi o caso, para ninguém usar o número errado.
if [ "$LIMITE_CPU" = "0" ]; then
    anotar "postgres_cpus" "SEM LIMITE -- o tempo abaixo NAO serve para dimensionar a janela"
else
    anotar "postgres_cpus" "$((LIMITE_CPU / 1000000000))"
fi

if [ "$LIMITE_MEM" = "0" ]; then
    anotar "postgres_memoria" "SEM LIMITE"
else
    anotar "postgres_memoria" "$((LIMITE_MEM / 1024 / 1024))MB"
fi

anotar "postgres_versao"  "$(pg 'SHOW server_version;' | head -1)"
anotar "mysql_versao"     "$(my 'SELECT VERSION();' | head -1)"
anotar "fuso_postgres"    "$(pg 'SHOW timezone;' | head -1)"
anotar "fuso_mysql"       "$(my 'SELECT @@session.time_zone;' | head -1)"

# ---------------------------------------------------------------------------
# A ORIGEM. É o critério de validade da comparação: se a origem mudou entre as
# duas execuções, elas não medem a mesma coisa e comparar os tempos engana.
# ---------------------------------------------------------------------------
secao "origem (impressao digital)"

for t in agendas vendas vendas_produtos clientes usuarios_acessos pontuacoes_historicos; do
    anotar "linhas_$t"  "$(my "SELECT COUNT(*) FROM \`$t\`;" | head -1)"
    anotar "maior_id_$t" "$(my "SELECT COALESCE(MAX(id),0) FROM \`$t\`;" | head -1)"
done

# ---------------------------------------------------------------------------
# As fases, cronometradas uma a uma.
#
# Por fase e não só o total: elas escalam de formas diferentes. Se a janela
# ficar apertada, é preciso saber ONDE apertar, e o total sozinho não diz.
# ---------------------------------------------------------------------------
secao "tempos (segundos)"

INICIO_TUDO=$(date +%s)

fase() {
    NOME="$1"
    shift
    printf '   %-24s ' "$NOME"
    I=$(date +%s)

    if ! "$@" > "ensaios/${ROTULO}-${NOME}.log" 2>&1; then
        F=$(date +%s)
        echo "FALHOU depois de $((F - I))s"
        anotar "$NOME" "FALHOU em $((F - I))s"
        echo
        echo "*** O ensaio parou em '$NOME'. Últimas linhas:"
        tail -20 "ensaios/${ROTULO}-${NOME}.log"
        exit 1
    fi

    F=$(date +%s)
    echo "$((F - I))s"
    anotar "$NOME" "$((F - I))"
}

limpeza() {
    docker compose -p "$PROJETO" exec -T mysql \
        mysql -uroot -proot "$BANCO_MYSQL" < scripts/pre-migracao-limpeza.sql
}

# O PASSO 3 QUE O DONO DECIDIU EM 06/10/2026, na parte que roda sempre: o
# dicionario de CEP esvaziado, com as 27 UFs repostas (D74, D430), e a migracao
# do Lider (D124). Fase propria, e nao dentro da `limpeza`: numa copia DA
# PRODUCAO, o DELETE de cerca de 758 mil linhas e o tempo que a janela ganhou, e
# ele tem de aparecer separado no registro.
#
# SO A PRIMEIRA EXECUCAO SOBRE A COPIA O MEDE. O que ele apaga nao volta, e a
# segunda execucao acha o dicionario vazio -- por isso a fase fica fora da lista
# de tempos de `comparar-ensaios.sh`, que acusaria a diferenca como ruido. A
# carga das duas e a mesma: nas duas o dicionario ja saiu antes do dump.
#
# Os UPDATE dos logins e do nascimento NAO entram: dependem da medida na
# replica, e a copia anonimizada reescreveu logins e datas. Quem os ensaia, com
# dados inventados, e `scripts/ensaiar-sql-do-passo-3.php`.
passo3() {
    docker compose -p "$PROJETO" exec -T mysql \
        mysql -uroot -proot "$BANCO_MYSQL" < scripts/limpar-dicionario-cep.sql || return 1
    docker compose -p "$PROJETO" exec -T mysql \
        mysql -uroot -proot "$BANCO_MYSQL" < scripts/migracao-grupo-lider-gerente.sql
}

schema() {
    php scripts/gerar-ddl-postgres.php > postgres/schema.sql
    docker compose -p "$PROJETO" exec -T postgres \
        psql -U "$USUARIO_PG" -d "$BANCO_PG" -q -c "DROP SCHEMA IF EXISTS bvip CASCADE;"
    docker compose -p "$PROJETO" exec -T postgres \
        psql -U "$USUARIO_PG" -d "$BANCO_PG" -v ON_ERROR_STOP=1 -q -f - < postgres/schema.sql
}

papeis()       { sh scripts/criar-papeis.sh; }
carga()        { sh scripts/migrar-dados.sh; }
conferencia()  { php scripts/conferir-migracao.php; }
provas_papeis(){ sh scripts/conferir-papeis.sh; }

# O 5b DO PROCEDIMENTO: a semente da vigencia do horario, depois da carga (F4.1,
# a revisao de 02/10/2026 -- o ensaio nao a tinha, e o tempo dela no tamanho de
# producao nao era medido). A ultima linha da semente e a contagem de quem ficou
# sem vigencia, e tem de ser 0; o `-q` cala o `SET` e o `INSERT`.
semente() {
    restantes=$(docker compose -p "$PROJETO" exec -T postgres \
        psql -U "$USUARIO_PG" -d "$BANCO_PG" -v ON_ERROR_STOP=1 -q -tA -f - < postgres/semente-usuarios-horarios.sql) || return 1

    echo "sem vigencia depois da semente: $restantes"
    [ "$(echo "$restantes" | tail -1)" = "0" ]
}

fase limpeza      limpeza
fase passo3       passo3
fase schema       schema
fase papeis       papeis
fase carga        carga
fase conferencia  conferencia
fase semente      semente
fase papeis_provas provas_papeis

# ---------------------------------------------------------------------------
# O comportamento. É o portão da fase inteira, e entra no ensaio porque o tempo
# dele conta na janela.
#
# Desde 06/10/2026 a suite NAO entra na janela (a resposta 7 do dono, o D378): o
# ensaio a roda em clones, com as fixtures so neles, e o tempo dela deixou de
# dimensionar a janela.
# ---------------------------------------------------------------------------

# O LOCAL.PHP DO CLONE: o modelo ($1) com o dbname trocado pelo do clone ($2). E o
# unico jeito de a suite ler o clone -- o app le `config/autoload/local.php` --, e
# por isso cada fase da suite o escreve antes e devolve o de antes no fim.
#
# O BANCO DO CLONE VAI ESCRITO, e o resto fica como no modelo (D469, desde
# 08/11/2026). O valor do `dbname` pode estar escrito (`'franquiabv_dev'`) ou
# vir do ambiente com um padrao (`getenv('DB_NAME') ?: 'franquiabv_dev'`, o do
# `docker/config/local.php.docker`, o do laboratorio): os dois viram o nome do
# clone, entre aspas. Com o `getenv` de antes, o `sed` nao achava o que trocar,
# e a suite leria o banco do `DB_NAME` do conteiner -- a origem. E a conferencia
# e pelo arquivo AVALIADO no conteiner: so vale se o PHP de la ler o clone.
apontar_para_o_clone() {
    sed -e "s/'dbname'[[:space:]]*=>[[:space:]]*\(getenv([^)]*)[[:space:]]*?:[[:space:]]*\)\{0,1\}'[^']*'/'dbname' => '$2'/" "$1" > config/autoload/local.php

    case "$(conexao_do_local_php)" in
        *"|$2|"?*) ;;
        *)
            echo "ABORTADO: o local.php do clone $2 nao foi escrito (o PHP do conteiner le: $(conexao_do_local_php || true))." >&2
            return 1
            ;;
    esac

    docker compose -p "$PROJETO" exec -T app sh -c 'rm -rf data/DoctrineORMModule/Proxy/*; true'
}

# As configuracoes que o banco grava com ALTER DATABASE, numa linha so.
configuracao_do_banco() {
    docker compose -p "$PROJETO" exec -T postgres psql -U "$USUARIO_PG" -d postgres -tA -c \
        "SELECT COALESCE(string_agg(c, '|' ORDER BY c), '') FROM pg_db_role_setting s JOIN pg_database d ON d.oid = s.setdatabase CROSS JOIN LATERAL unnest(s.setconfig) c WHERE d.datname = '$1' AND s.setrole = 0;"
}

# O CLONE DO POSTGRESQL CARREGADO (D378): o banco da carga, ja com a semente,
# copiado por TEMPLATE, e as fixtures SO na copia. A origem fica como a carga a
# deixou -- o "resultado da carga" do fim a le --, e o que a suite escreve fica
# no clone.
#
# O TEMPLATE PEDE A ORIGEM SEM NINGUEM LIGADO: aqui o local.php ainda aponta para
# o MySQL. Uma sessao aberta nela (um adminer, um psql) faz o CREATE DATABASE
# falhar, e a fase para dizendo isso.
#
# O QUE O TEMPLATE NAO COPIA: as configuracoes do banco e os privilegios do banco.
# As configuracoes sao as duas que o `postgres/schema.sql` grava com ALTER
# DATABASE -- o search_path e o fuso; sem o fuso, o clone leria as datas da carga
# tres horas fora --, e a fase confere que o clone ficou com as mesmas da origem:
# se o gerador passar a gravar outra, ela para em vez de rodar a suite num banco
# diferente. Os privilegios sao os do `criar-papeis.sh`, rodado sobre o clone; ele
# e idempotente, e a senha e a mesma da fase `papeis`.
clonar_postgres() {
    docker compose -p "$PROJETO" exec -T postgres psql -U "$USUARIO_PG" -d postgres -v ON_ERROR_STOP=1 -q \
        -c "DROP DATABASE IF EXISTS \"$CLONE_PG\";" \
        -c "CREATE DATABASE \"$CLONE_PG\" TEMPLATE \"$BANCO_PG\";" || return 1

    docker compose -p "$PROJETO" exec -T postgres psql -U "$USUARIO_PG" -d postgres -v ON_ERROR_STOP=1 -q \
        -c "ALTER DATABASE \"$CLONE_PG\" SET search_path = bvip, public;" \
        -c "ALTER DATABASE \"$CLONE_PG\" SET timezone = 'America/Sao_Paulo';" || return 1

    config_origem=$(configuracao_do_banco "$BANCO_PG") || return 1
    config_clone=$(configuracao_do_banco "$CLONE_PG") || return 1

    if [ "$config_origem" != "$config_clone" ]; then
        echo "ABORTADO: o clone nao ficou com a configuracao da origem." >&2
        echo "  $BANCO_PG: $config_origem" >&2
        echo "  $CLONE_PG: $config_clone" >&2
        return 1
    fi

    PROJETO="$PROJETO" BANCO_PG="$CLONE_PG" USUARIO_PG="$USUARIO_PG" sh scripts/criar-papeis.sh || return 1

    # As fixtures como o `rodar-caracterizacao-postgres.sh` as carrega: o inquilino
    # antes (o RLS), sem o `SET NAMES` do MySQL, parando no primeiro erro -- um
    # banco pela metade daria uma suite verde e mentindo --, e as sequencias
    # avancadas depois, porque o id explicito nao as move.
    [ -f tests/Caracterizacao/fixtures.sql ] || return 1
    { echo "SET app.inquilino_id = 1;"; grep -v "^SET NAMES" tests/Caracterizacao/fixtures.sql; } |
        docker compose -p "$PROJETO" exec -T postgres psql -U "$USUARIO_PG" -d "$CLONE_PG" -q -v ON_ERROR_STOP=1 -f - || return 1
    docker compose -p "$PROJETO" exec -T postgres psql -U "$USUARIO_PG" -d "$CLONE_PG" -q -v ON_ERROR_STOP=1 -f - \
        < postgres/repontar-sequencias.sql
}

fase clone_pg     clonar_postgres

apontar_postgres() {
    # O que se guarda TEM que ser a configuracao do MySQL. Se uma execucao
    # anterior morreu depois desta fase, o local.php ficou no PostgreSQL -- e
    # guardar isso faria a volta atras "restaurar" o PostgreSQL, rodar os 67
    # testes contra ele e reportar OK. Verde, e provando nada.
    #
    # A mesma conferencia do comeco do roteiro, de novo aqui: entre ela e esta
    # fase passam a carga e a conferencia, que no tamanho da producao levam
    # horas, e o local.php e de quem estiver na maquina.
    if ! local_php_e_o_da_origem; then
        echo "ABORTADO: config/autoload/local.php nao aponta para o MySQL de $BANCO_MYSQL." >&2
        echo "Alguem o trocou durante o ensaio. Restaure-o antes de repetir." >&2
        return 1
    fi

    # Daqui em diante o trap desfaz o que este processo trocar.
    MEXI_NO_LOCAL_PHP=1
    cp config/autoload/local.php ensaios/local.mysql.guardado
    cp config/autoload/local.postgres.php.dist config/autoload/local.php

    # E confirme que a troca aconteceu: sem isto, um cp que falhasse deixaria a
    # fase seguinte medindo o MySQL e chamando de PostgreSQL.
    grep -q "'user' => 'bvip_app'" config/autoload/local.php || {
        echo "ABORTADO: a troca para PostgreSQL nao aconteceu." >&2
        return 1
    }

    docker compose -p "$PROJETO" exec -T app sh -c 'rm -rf data/DoctrineORMModule/Proxy/*; true'
}

# A SUITE NO CLONE DO POSTGRESQL (D378), com o local.php dele; o que a fase
# `apontar` escreveu volta no fim, passe a suite ou nao.
testes() {
    apontar_para_o_clone config/autoload/local.postgres.php.dist "$CLONE_PG" || return 1

    docker compose -p "$PROJETO" exec -T app phpunit --testsuite caracterizacao
    resultado=$?

    cp config/autoload/local.postgres.php.dist config/autoload/local.php
    docker compose -p "$PROJETO" exec -T app sh -c 'rm -rf data/DoctrineORMModule/Proxy/*; true'

    return $resultado
}

# O 7b: os dois roteiros da vigencia leem o banco do `local.php`, e por isso so
# rodam DEPOIS de apontar. A plataforma declarada os faz sair com 2 se o
# `local.php` nao estiver no PostgreSQL. O ensaio escreve numa transacao que ele
# mesmo desfaz.
vigencia() {
    docker compose -p "$PROJETO" exec -T app php scripts/conferir-vigencia-do-horario.php --plataforma=postgresql &&
        docker compose -p "$PROJETO" exec -T app php scripts/ensaiar-vigencia-do-horario.php --plataforma=postgresql
}

fase apontar      apontar_postgres
fase vigencia     vigencia
fase caracterizacao testes

# ---------------------------------------------------------------------------
# O ENSAIO DA VOLTA ATRÁS. Metade do procedimento de corte é poder desistir, e
# essa metade nunca é ensaiada -- descobre-se que não funciona no dia em que
# ela é a única saída.
# ---------------------------------------------------------------------------

# O CLONE DO MYSQL, para a suite da volta atras (D378). A volta atras aponta de
# volta para o MySQL da copia, e a suite de la tambem pede as fixtures -- que
# tambem nao podem ir a origem: mudariam a impressao digital dela, e a segunda
# execucao sairia anulada. O MySQL nao tem TEMPLATE: e o dump da origem num banco
# novo, com o charset dela, e as fixtures por cima. A origem ja esta como a
# volta atras a encontra: depois da `limpeza` e do `passo3`, as ultimas escritas
# do roteiro nela antes da semente da volta atras -- que, numa copia ja semeada,
# acrescenta zero.
#
# Os eventos ficam de fora (o `mysqldump` sem `--events`): o do push apagaria
# linhas do clone enquanto a suite roda. E o pipe vai num bash com `pipefail`:
# sem ele quem sai e o `mysql` do fim, e um dump que morresse no meio deixaria o
# clone pela metade sem erro nenhum.
# O usuario do app no MySQL, lido do local.php guardado (o da origem): o GRANT do
# clone e o REVOKE do fim sao para ele. Lido pelo PHP do conteiner, como a
# guarda do comeco (D469): no modelo do Docker o usuario vem do `DB_USER`.
usuario_do_app_no_mysql() {
    conexao_do_local_php ensaios/local.mysql.guardado | cut -d'|' -f3
}

clonar_mysql() {
    usuario=$(usuario_do_app_no_mysql)
    conjunto=$(my "SELECT CONCAT(DEFAULT_CHARACTER_SET_NAME, ' COLLATE ', DEFAULT_COLLATION_NAME) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '$BANCO_MYSQL';" | head -1)

    if [ -z "$usuario" ] || [ -z "$conjunto" ]; then
        echo "ABORTADO: nao li o usuario do local.php guardado ou o charset de $BANCO_MYSQL." >&2
        return 1
    fi

    docker compose -p "$PROJETO" exec -T mysql mysql -uroot -proot -e \
        "DROP DATABASE IF EXISTS \`$CLONE_MYSQL\`; CREATE DATABASE \`$CLONE_MYSQL\` CHARACTER SET $conjunto; GRANT ALL ON \`$CLONE_MYSQL\`.* TO '$usuario'@'%';" || return 1

    docker compose -p "$PROJETO" exec -T mysql bash -o pipefail -c \
        "mysqldump -uroot -proot --single-transaction --quick --no-tablespaces --set-gtid-purged=OFF '$BANCO_MYSQL' | mysql -uroot -proot '$CLONE_MYSQL'" || return 1

    docker compose -p "$PROJETO" exec -T mysql mysql -uroot -proot "$CLONE_MYSQL" < tests/Caracterizacao/fixtures.sql
}

fase clone_mysql  clonar_mysql

voltar() {
    cp ensaios/local.mysql.guardado config/autoload/local.php

    # A outra ponta da mesma guarda: os 67 testes so provam a volta atras se
    # estiverem rodando contra o MySQL de verdade -- e o conferidor da vigencia,
    # abaixo, so mede a origem se o restaurado for o dela.
    local_php_e_o_da_origem || {
        echo "ABORTADO: depois de restaurar, local.php nao aponta para o MySQL de $BANCO_MYSQL." >&2
        return 1
    }

    docker compose -p "$PROJETO" exec -T app sh -c 'rm -rf data/DoctrineORMModule/Proxy/*; true'

    # A vigencia do horario na volta atras, como o procedimento manda: a semente
    # do MySQL (aqui, numa copia ja semeada, ela acrescenta zero) e o conferidor,
    # depois do `local.php` restaurado.
    docker compose -p "$PROJETO" exec -T mysql \
        mysql -uroot -proot "$BANCO_MYSQL" < scripts/semente-usuarios-horarios.mysql.sql || return 1
    docker compose -p "$PROJETO" exec -T app php scripts/conferir-vigencia-do-horario.php --plataforma=mysql || return 1

    # A suite, no clone do MySQL (D378), com o local.php dele; o restaurado volta
    # no fim, passe a suite ou nao. O conferidor acima ja leu a origem pelo
    # local.php restaurado.
    apontar_para_o_clone ensaios/local.mysql.guardado "$CLONE_MYSQL" || return 1

    docker compose -p "$PROJETO" exec -T app phpunit --testsuite caracterizacao
    resultado=$?

    cp ensaios/local.mysql.guardado config/autoload/local.php
    docker compose -p "$PROJETO" exec -T app sh -c 'rm -rf data/DoctrineORMModule/Proxy/*; true'

    return $resultado
}

fase volta_atras  voltar

FIM_TUDO=$(date +%s)
anotar "TOTAL" "$((FIM_TUDO - INICIO_TUDO))"

# OS CLONES SAEM NO FIM: cada um e uma copia inteira da base. Se o ensaio parar
# antes, eles ficam -- servem para olhar o que a suite viu --, e a execucao
# seguinte os apaga e refaz. Fora do TOTAL, como a suite fora da janela.
#
# O DROP DATABASE DO MYSQL NAO TIRA O GRANT (a verificacao do D378, 11/10/2026:
# a prova sobre bancos descartaveis deixou a linha do clone em `mysql.db`). Sem
# o REVOKE, cada ensaio deixava o usuario do app com ALL num banco que voltaria
# a valer se alguem criasse outro com esse nome. O `IF EXISTS` (MySQL 8.0.30 em
# diante) cala o caso em que o GRANT ja saiu.
apagar_clones() {
    usuario=$(usuario_do_app_no_mysql)

    if [ -z "$usuario" ]; then
        echo "ABORTADO: nao li o usuario do local.php guardado." >&2
        return 1
    fi

    docker compose -p "$PROJETO" exec -T postgres psql -U "$USUARIO_PG" -d postgres -v ON_ERROR_STOP=1 -q \
        -c "DROP DATABASE IF EXISTS \"$CLONE_PG\";" || return 1
    docker compose -p "$PROJETO" exec -T mysql mysql -uroot -proot -e \
        "DROP DATABASE IF EXISTS \`$CLONE_MYSQL\`; REVOKE IF EXISTS ALL ON \`$CLONE_MYSQL\`.* FROM '$usuario'@'%';"
}

fase apagar_clones apagar_clones

# ---------------------------------------------------------------------------
# O resultado da carga, para comparar entre execuções. Não são os tempos: são
# os NÚMEROS. Duas execuções com tempos parecidos e contagens diferentes é o
# pior dos casos, e o mais fácil de não perceber.
# ---------------------------------------------------------------------------
secao "resultado da carga"

for t in agendas vendas vendas_produtos clientes usuarios_acessos pontuacoes_historicos; do
    anotar "destino_$t" "$(pg "SELECT COUNT(*) FROM $t;" | head -1)"
done

anotar "sequencia_vendas"  "$(pg "SELECT last_value FROM vendas_id_seq;" | head -1)"
anotar "data_min_vendas"   "$(pg "SELECT to_char(MIN(data_criacao),'YYYY-MM-DD HH24:MI:SS') FROM vendas;" | head -1)"
anotar "data_max_vendas"   "$(pg "SELECT to_char(MAX(data_criacao),'YYYY-MM-DD HH24:MI:SS') FROM vendas;" | head -1)"
anotar "soma_vendas"       "$(pg "SELECT SUM(round(valor_total::numeric,6)) FROM vendas;" | head -1)"

# Prefixo `portao_` de proposito: sem ele, "caracterizacao" seria rotulo do
# TEMPO da fase e tambem do VEREDITO, e quem procurasse a chave pegaria a
# primeira ocorrencia. O comparador acabava comparando tempos achando que
# comparava vereditos, e acusava diferenca onde nao havia nenhuma.
#
# O VEREDITO DA SUITE TEM DUAS FORMAS no PHPUnit 10: "OK (N tests, M assertions)",
# e, com caso pulado, "OK, but some tests were skipped!" seguido de "Tests: N,
# Assertions: M, Skipped: K." -- e a suite de hoje pula tres casos de proposito
# (as telas sem o botao, no D199). So a primeira forma era lida, e o portao saia
# EM BRANCO de uma suite verde; o comparador aceita branco contra branco calado
# (D378, 11/10/2026). Aqui so se chega com as duas fases verdes.
veredito_da_suite() {
    grep -oE 'OK \([0-9]+ tests, [0-9]+ assertions\)|Tests: [0-9]+, Assertions: [0-9]+[^.]*' "$1" | head -1
}

secao "portoes"
anotar "portao_conferir_migracao" "OK"
anotar "portao_conferir_papeis"   "OK"
anotar "portao_vigencia"          "OK"
anotar "portao_caracterizacao"    "$(veredito_da_suite "ensaios/${ROTULO}-caracterizacao.log")"
anotar "portao_volta_atras"       "$(veredito_da_suite "ensaios/${ROTULO}-volta_atras.log")"

echo
echo "Registro em $REGISTRO"
echo
cat "$REGISTRO"
