#!/usr/bin/env bash
# tools/check.sh — caminho dourado: a ÚNICA cadeia de validação do projeto.
#
# Roda TODAS as verificações em uma passada, SEM parar na 1ª falha, e devolve o relatório
# acumulado no stdout. É autossuficiente: não depende de alvos do Makefile (ADR-038/ADR-040).
#
# Variáveis:
#   BASE_URL=...          URL do app para e2e/a11y/perf (default: http://localhost:$WEB_PORT do .env)
#   CHECK_QUIET=1         não transmite a saída de cada check (só o resumo + falhas)
#   CHECK_ASSETS=1        força `npm ci && npm run build` mesmo com build presente e atual
#   CHECK_FORCE_PREP=1    roda TODO passo caro (autoload/migrate/optimize/seed), ignorando os stamps
#   CHECK_LOGDIR=...      diretório dos logs por check (default: build/check)
#   SNIFF_A11Y_STRICT=0   relaxa o gate a11y (default: 1 = estrito)
#   COVERAGE_MIN=...      piso de cobertura de `src/` (default: 90)
#   COVERAGE_WORKERS=4    processos do PHPUnit paralelo (ParaTest; 1..16)
#   CHECK_E2E=1 / --e2e   inclui o e2e Playwright + perf (opt-in; obrigatório ao fechar o escopo)
#
# O e2e (Playwright+axe) é o check mais lento (~75 s com 4 workers; era ~174 s serial) e só é afetado
# por mudança de UI/HTML/CSS/JS/rota — por isso é **opt-in**. `make check E2E=1` o liga.
#
# Passos caros e idempotentes são **gated por staleness** (autoload, migrate, optimize, assets): num
# run sem mudança de código eles viram SKIP e o ciclo cai de ~6 min para ~2 min (sem E2E) — a
# cobertura de validação não muda, só o trabalho repetido.
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"

# Valor de uma chave do `.env` (sem aspas); fallback quando ausente — o compose usa `WEB_PORT` e
# a URL tem de acompanhar, senão o check mede outra porta (achado F5.I2.T2-B).
env_value() {
    local key="$1" fallback="$2" value
    value="$(grep -E "^${key}=" .env 2>/dev/null | tail -1 | cut -d= -f2- | tr -d "\"' ")"
    printf '%s' "${value:-$fallback}"
}

BASE_URL="${BASE_URL:-http://localhost:$(env_value WEB_PORT 50700)}"
STRICT="${SNIFF_A11Y_STRICT:-1}"
QUIET="${CHECK_QUIET:-0}"
LOGDIR="${CHECK_LOGDIR:-$ROOT/build/check}"
E2E="${CHECK_E2E:-0}"

for arg in "$@"; do
    case "$arg" in
        --e2e) E2E=1 ;;
        --no-e2e) E2E=0 ;;
        -h|--help) printf 'uso: tools/check.sh [--e2e]\n'; exit 0 ;;
    esac
done

# Exportado: `tools/e2e.sh` e `tools/sniff-gate.sh` leem do ambiente — o default tem de ser o
# mesmo dos dois e do Playwright, senão o `make check` acha o app offline e **pula** e2e/a11y.
export BASE_URL SNIFF_BASE_URL="${SNIFF_BASE_URL:-$BASE_URL}"

PROJECT="$(bash "$ROOT/tools/project-name.sh" 2>/dev/null)"
NODE_IMAGE="node:26-alpine@sha256:6f08a523443ddedf52941874c203475b0ad503e859ad70cb26ab8be9a517dbd5"

# Runtime único (dashboard abaixo): o mesmo `docker`/`podman compose` do Makefile. No podman,
# `COMPOSE_FILES` inclui o override (keep-id + label=disable) e `MOUNT_LABEL` fica vazio.
# shellcheck source=tools/runtime.sh
. "$ROOT/tools/runtime.sh"

# Helpers (mesma definição do Makefile, para o script ser autossuficiente).
dc() { "${ENGINE[@]}" compose -p "$PROJECT" --env-file .env "${COMPOSE_FILES[@]}" "$@"; }
nod() { "${ENGINE[@]}" run --rm "${RUN_ARGS[@]}" -u "$(id -u):$(id -g)" -e HOME=/tmp -v "$ROOT":/app"$MOUNT_LABEL" -w /app "$NODE_IMAGE" sh -c "$1"; }

mkdir -p "$LOGDIR"
rm -f "$LOGDIR"/*.log 2>/dev/null || true

if [ -t 1 ]; then
    BOLD=$'\033[1m'; DIM=$'\033[2m'; RED=$'\033[31m'; GRN=$'\033[32m'; YEL=$'\033[33m'; RST=$'\033[0m'
else
    BOLD=''; DIM=''; RED=''; GRN=''; YEL=''; RST=''
fi

names=(); statuses=(); durations=(); logs=()
fail=0

# run <nome> <comando> — roda um check, acumula o resultado; nunca aborta a cadeia.
run() {
    local name="$1" cmd="$2"
    local log="$LOGDIR/$name.log"
    printf '\n%s▶ %s%s\n' "$BOLD" "$name" "$RST"
    local start=$SECONDS st
    if [ "$QUIET" = "1" ]; then
        if eval "$cmd" >"$log" 2>&1; then st=OK; else st=FAIL; fail=1; fi
    else
        if eval "$cmd" > >(tee "$log") 2>&1; then st=OK; else st=FAIL; fail=1; fi
    fi
    local dur=$((SECONDS - start))
    names+=("$name"); statuses+=("$st"); durations+=("${dur}s"); logs+=("$log")
    if [ "$st" = "OK" ]; then
        printf '  %s✓ %s%s %s(%ss)%s\n' "$GRN" "$name" "$RST" "$DIM" "$dur" "$RST"
    else
        printf '  %s✗ %s%s %s(%ss)%s\n' "$RED" "$name" "$RST" "$DIM" "$dur" "$RST"
    fi
}

# fail_check <nome> <motivo> — falha deliberada (ex.: `E2E=1` pedido com o app offline).
fail_check() {
    local name="$1" why="$2"
    local log="$LOGDIR/$name.log"
    printf '%s\n' "$why" >"$log"
    names+=("$name"); statuses+=("FAIL"); durations+=("-"); logs+=("$log")
    fail=1
    printf '  %s✗ %s%s\n' "$RED" "$name" "$RST"
}

# skip <nome> <motivo> — registra um check não executado (não conta como falha).
skip() {
    local name="$1" why="$2"
    names+=("$name"); statuses+=("SKIP"); durations+=("-"); logs+=("")
    printf '\n%s⊘ %s — SKIP (%s)%s\n' "$YEL" "$name" "$why" "$RST"
}

# URL/host do painel derivados do portal (ADR-005/ADR-037): uma fonte para a11y e borda.
cms_base_url() { printf '%s' "$BASE_URL" | sed -E 's#^([a-z]+://)#\1cms.#'; }
portal_host() { printf '%s' "$BASE_URL" | sed -E 's#^[a-z]+://([^/:]+).*#\1#'; }

# Tolerante a lentidão transitória (o container pode estar saturado após coverage/optimize).
online() {
    local code=000 attempt
    for attempt in 1 2 3; do
        code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 15 "$BASE_URL" 2>/dev/null || echo 000)"
        [ "$code" = "200" ] && break
        sleep 2
    done
    printf '\n%sreachability: %s -> HTTP %s%s\n' "$DIM" "$BASE_URL" "$code" "$RST"

    [ "$code" = "200" ]
}

# Build de assets **fresco**: o manifesto cobre o que as views pedem (`AssetsManifestTest` prova o
# conteúdo), mas um manifesto velho falha os testes com erro obscuro de "Unable to locate file in
# Vite manifest". Rebuild quando qualquer fonte de asset/config do build for mais nova que ele.
assets_stale() {
    local manifest="public/build/manifest.json" now
    [ -f "$manifest" ] || return 0

    now="$(find src -path '*/Assets/*' -type f -newer "$manifest" -print -quit 2>/dev/null)"
    [ -n "$now" ] && return 0

    local file
    for file in vite.config.js package.json package-lock.json; do
        [ -f "$file" ] && [ "$file" -nt "$manifest" ] && return 0
    done

    return 1
}

# Autoload: `composer dump-autoload` só quando alguma classe é mais nova que o classmap.
FORCE_PREP="${CHECK_FORCE_PREP:-0}"

# Autoload: `composer dump-autoload` só quando um arquivo PHP é mais novo que o stamp. O classmap
# não serve de stamp: o Composer **não o reescreve** quando nenhuma classe mudou, então o passo
# caro rodaria em todo check (~6 s).
AUTOLOAD_STAMP="storage/framework/.check-autoload"
autoload_stale() {
    [ "$FORCE_PREP" = "1" ] && return 0
    [ -f "$AUTOLOAD_STAMP" ] || return 0
    # `bootstrap/cache/*` é **escrito pelos próprios checks** (manifesto de módulos, packages,
    # services): nunca é sinal de classe nova — só o que está fora do cache conta.
    [ -n "$(find src app bootstrap config database routes tests -name '*.php' \
        -not -path 'bootstrap/cache/*' -not -path '*/Lang/*' -newer "$AUTOLOAD_STAMP" -print -quit 2>/dev/null)" ]
}

# Migrações: `artisan migrate` (boot ~8 s) só quando há arquivo de migração mais novo que o stamp.
MIGRATE_STAMP="storage/framework/.check-migrate"
migrate_stale() {
    [ "$FORCE_PREP" = "1" ] && return 0
    [ -f "$MIGRATE_STAMP" ] || return 0
    [ -n "$(find src app database -path '*igrations*' -name '*.php' -newer "$MIGRATE_STAMP" -print -quit 2>/dev/null)" ]
}

# `optimize` (config+route+event+view, ~30 s) só quando um insumo mudou desde o último verde.
OPTIMIZE_STAMP="storage/framework/.check-optimize"
optimize_stale() {
    [ "$FORCE_PREP" = "1" ] && return 0
    [ -f "$OPTIMIZE_STAMP" ] || return 0
    [ -n "$(find config routes bootstrap src app -name '*.php' \
        -not -path 'bootstrap/cache/*' -not -path '*/Lang/*' -newer "$OPTIMIZE_STAMP" -print -quit 2>/dev/null)" ]
}

# Edge: o container `web` só reinicia quando o Caddyfile é **mais novo** que o `StartedAt`.
# Sem isso, `caddy validate` aprova o arquivo novo enquanto o processo serve o antigo (risk_01qke8zj).
CADDYFILE="docker/web/Caddyfile"
caddy_stale() {
    [ "$FORCE_PREP" = "1" ] && return 0
    [ -f "$CADDYFILE" ] || return 1
    local id started
    id="$(dc ps -q web 2>/dev/null || true)"
    [ -n "$id" ] || return 1
    started="$("${ENGINE[@]}" inspect -f '{{.State.StartedAt}}' "$id" 2>/dev/null || true)"
    [ -n "$started" ] || return 0
    [ "$(stat -c %Y "$CADDYFILE")" -gt "$(date -d "$started" +%s)" ]
}

# Um único `artisan`: o orquestrador `Demonstration\CoreDatabaseSeeder` roda todos os seeders
# (ADR-041). Antes eram 5 chamadas = 5 boots do framework, ~41 s medidos.
seed_demo() {
    dc exec -T php-fpm php artisan db:seed --force
}

# Bucket S3 (RustFS) é idempotente mas custa ~8 s (cliente S3/metadata): stamp por config/.env.
# Se o bucket for apagado por fora e o stamp ficar, o e2e falha alto — nunca falso verde.
STORAGE_STAMP="storage/framework/.check-storage"
storage_stale() {
    [ "$FORCE_PREP" = "1" ] && return 0
    [ -f "$STORAGE_STAMP" ] || return 0
    [ -n "$(find config/filesystems.php src/core/Internal/System/Storage .env -newer "$STORAGE_STAMP" -print -quit 2>/dev/null)" ]
}

# Seed demo é idempotente e custa ~8 s: só roda quando o **próprio seed** (ou uma migração)
# mudou desde o último verde. Pular pode causar falha em teste que exige demo data, nunca um
# falso verde (o seed é idempotente e o stamp só avança depois do sucesso).
DEMO_STAMP="storage/framework/.check-demo"
demo_stale() {
    [ "$FORCE_PREP" = "1" ] && return 0
    [ -f "$DEMO_STAMP" ] || return 0
    [ -n "$(find src/demonstration database -name '*.php' -newer "$DEMO_STAMP" -print -quit 2>/dev/null)" ]
}

# Cadeia de timeout do upload (D0): a prova de ponta a ponta custa ~2 min (100 MiB a 2 MB/s
# nos dois sentidos), então só roda quando a **cadeia** mudou. Pular é seguro: a correção
# só pode regredir mexendo nesses 4 arquivos, e o gate de ordem (`security-check`) continua
# rodando sempre — ele é o guard rápido, este é a prova cara.
UPLOAD_CHAIN_STAMP="storage/framework/.check-upload-chain"
upload_chain_stale() {
    [ "$FORCE_PREP" = "1" ] && return 0
    [ -f "$UPLOAD_CHAIN_STAMP" ] || return 0
    [ -n "$(find docker/web/Caddyfile docker/web/app.conf docker/php-fpm/php.ini \
        docker/php-fpm/zz-custom.conf -newer "$UPLOAD_CHAIN_STAMP" -print -quit 2>/dev/null)" ]
}

# Correções preventivas (evita turnos): aplica Pint + Prettier/ESLint --fix ANTES de validar e
# registra o ANTES (não-conformidades) e o DEPOIS (os checks `lint-php`/`lint-js` provam).
normalize() {
    local php_before js_before php_rc=0 js_rc=0

    php_before="$(dc exec -T php-fpm ./vendor/bin/pint --test 2>&1)" || php_rc=$?
    js_before="$(nod 'npm run lint' 2>&1)" || js_rc=$?

    printf '\n%s== ANTES (não-conformidades de estilo) ==%s\n' "$DIM" "$RST"
    printf '%s\n' "$php_before" | grep -E '⨯|style issue|FAIL' || echo '  (PHP/Pint: em conformidade)'
    printf '%s\n' "$js_before" | grep -E '✖|\[warn\]|error|warning' | head -20 || echo '  (JS/Prettier+ESLint: em conformidade)'

    # Só roda o fixer quando o `--test` acusou drift: num run limpo eram dois scans completos.
    printf '\n%s== CORRIGINDO (apenas o que precisa) ==%s\n' "$DIM" "$RST"
    if [ "$php_rc" -ne 0 ]; then
        dc exec -T php-fpm ./vendor/bin/pint 2>&1 | grep -E 'FIXED|style issue|PASS' || echo '  (PHP/Pint: nada a corrigir)'
    else
        echo '  (PHP/Pint: em conformidade, fixer não necessário)'
    fi
    if [ "$js_rc" -ne 0 ]; then
        nod 'npm run lint:fix' 2>&1 | grep -E '✖|fixed|error|warning|problems' | head -20 || echo '  (JS: nada a corrigir)'
    else
        echo '  (JS: em conformidade, fixer não necessário)'
    fi

    printf '%s== DEPOIS: provado pelos checks `lint-php`/`lint-js` ==%s\n' "$DIM" "$RST"
}

optimize_verify() {
    dc exec -T php-fpm sh -c 'set -e; \
        php artisan optimize >/dev/null; \
        for f in bootstrap/cache/config.php bootstrap/cache/routes-v7.php bootstrap/cache/events.php; do \
            test -e "$f" || { echo "ERRO: $f não gerado"; exit 1; }; \
        done; \
        ls storage/framework/views/*.php >/dev/null 2>&1 || { echo "ERRO: views não compiladas"; exit 1; }; \
        php artisan optimize:clear >/dev/null; \
        echo "optimize-verify: OK (config/route/event/view cacheados e limpos)"'
}

# PHPUnit paralelo (ParaTest) com cobertura mesclada. O script isola os bancos por worker e
# recria o schema base; o gate `coverage-check` lê o clover final (ADR-038).
coverage_run() {
    mkdir -p build/coverage
    tools/coverage-parallel.sh
}

a11y_gate() {
    local cms_url key_pages
    cms_url="$(cms_base_url)"
    cms_url="${cms_url%/}/login"
    key_pages="${SNIFF_KEY_PAGES:-/ /dados-abertos}"

    KEY_PAGES="$key_pages" SNIFF_VIEWPORT=1366x768 tools/sniff-gate.sh &&
        KEY_PAGES="$key_pages" SNIFF_VIEWPORT=390x844 tools/sniff-gate.sh &&
        SNIFF_BASE_URL="$cms_url" KEY_PAGES='' SNIFF_VIEWPORT=1366x768 tools/sniff-gate.sh &&
        SNIFF_BASE_URL="$cms_url" KEY_PAGES='' SNIFF_VIEWPORT=390x844 tools/sniff-gate.sh
}

# Só a metade de navegador: o orçamento de bytes/N+1 (`PerformanceBudgetTest`) já rodou dentro do
# `coverage` — repetir aqui custava um boot do framework (~8 s) sem cobrir nada novo.
#
# `--no-deps` é obrigatório: sem ele o Playwright reexecuta o lote `cms-serial` (dependencies) só
# para medir o `11-performance`, repetindo 18/23/26/31/32 (~80 s e escrita duplicada no banco). O
# e2e já rodou antes neste mesmo `make check E2E=1`.
perf_run() {
    BASE_URL="$BASE_URL" tools/e2e.sh test 11-performance --no-deps
}

printf '%s========== make check — cadeia completa ==========%s\n' "$BOLD" "$RST"
printf '%sapp: %s · logs: %s · quiet: %s%s\n' "$DIM" "$BASE_URL" "$LOGDIR" "$QUIET" "$RST"

# ── 0. Precondições (banco de teste, assets, dados demo, autoload) ───────────────────
# Caches de config/rota/view nunca podem sobreviver ao perfil de teste: com `config:cache` o
# `env('DB_DATABASE')` do PHPUnit fica congelado no banco de dev (ADR-001/ADR-008). Só paga o
# clear quando existe cache — num run limpo ele não custa nada.
# Views compiladas são invalidadas por mtime e o e2e/a11y as recria a cada run; só os caches de
# bootstrap (config/rota/evento) congelam o perfil de teste.
cache_stale() {
    dc exec -T php-fpm sh -c 'test -f bootstrap/cache/config.php || test -f bootstrap/cache/routes-v7.php || test -f bootstrap/cache/events.php'
}

if cache_stale; then
    run "prep:cache" 'dc exec -T php-fpm php artisan optimize:clear'
else
    skip "prep:cache" "sem cache de config/rota/evento"
fi
run "prep:test-db" 'tools/test-db.sh'
# `make up` é compose puro: o bucket S3 (RustFS) entra como pré-condição idempotente do check.
if storage_stale; then
    run "prep:storage" "dc exec -T php-fpm php artisan storage:ensure-bucket && mkdir -p storage/framework && touch $STORAGE_STAMP"
else
    skip "prep:storage" "bucket/config inalterados desde o último verde"
fi
# Bucket do backup **offsite da mídia** (D7): o gate `media-backup` faz `exists()` no destino e um
# bucket ausente falha (`UnableToCheckExistence`) assim que existe mídia no inventário — inclusive
# mídia deixada pelo e2e. Idempotente e barato; roda sempre, fora do stamp (o volume pode ser
# recriado com o stamp fresco). Sem `OFFSITE_MEDIA_BUCKET` o disco é opcional e o comando não falha.
run "prep:storage-offsite" "dc exec -T php-fpm php artisan storage:ensure-bucket --disk=offsite-media --optional"
# Esquema do app **em dia** antes de semear/testar: migração pendente derruba o e2e com 500 (a
# página existe no código, a tabela não existe no banco) e o erro aparece longe da causa. `migrate`
# é idempotente (spec 10) — rodar sempre é barato e deixa o caminho dourado autossuficiente.
if migrate_stale; then
    run "prep:migrate" "dc exec -T php-fpm php artisan migrate --force && mkdir -p storage/framework && touch $MIGRATE_STAMP"
else
    skip "prep:migrate" "nenhuma migração nova desde o último verde"
fi
if [ "${CHECK_ASSETS:-0}" = "1" ] || assets_stale; then
    run "prep:assets" "nod 'npm ci && npm run build'"
else
    skip "prep:assets" "build presente e atual (CHECK_ASSETS=1 para forçar)"
fi
if demo_stale; then
    run "prep:demo-data" "seed_demo && mkdir -p storage/framework && touch $DEMO_STAMP"
else
    skip "prep:demo-data" "dados demo inalterados desde o último verde"
fi
if autoload_stale; then
    run "prep:autoload" "dc exec -T php-fpm composer dump-autoload -q && mkdir -p storage/framework && touch $AUTOLOAD_STAMP"
else
    skip "prep:autoload" "stamp atual (nenhuma classe nova/modificada)"
fi
if caddy_stale; then
    run "prep:edge" "dc restart web >/dev/null && for i in \$(seq 1 20); do curl -fsS -o /dev/null '$BASE_URL/' 2>/dev/null && break; sleep 0.5; done; curl -fsS -o /dev/null '$BASE_URL/'"
else
    skip "prep:edge" "Caddyfile mais antigo que o StartedAt do container"
fi

# ── 1. Normalização preventiva + estilo e documentação ────────────────────────────────
run "prep:normalize" 'normalize'
run "docs-check" 'tools/docs-check.sh'
run "lint-php" 'dc exec -T php-fpm ./vendor/bin/pint --test'
run "lint-js" "nod 'npm run lint'"

# ── 2. Análise estática e arquitetura ─────────────────────────────────────────────────
run "static" 'dc exec -T php-fpm ./vendor/bin/phpstan analyse --memory-limit=1G --no-progress'
run "check-boundaries" 'dc exec -T php-fpm php tools/arch-check'
run "check-migrations" 'dc exec -T php-fpm php tools/migration-check'
run "check-vendor" 'dc exec -T php-fpm bash tools/vendor-check.sh && dc exec -T php-fpm php tools/assets-licenses-check.php'
run "check-gates" 'dc exec -T php-fpm bash tools/gates-selftest.sh'
run "edge-config" 'dc exec -T web caddy validate --config /etc/caddy/Caddyfile'
# O isolamento de rede é um contrato medido (D5): o `web` não fala com o banco, o cache
# nem o storage; o app fala; os dados não têm saída para a internet.
run "network-check" 'bash tools/network-check.sh'
# O contrato do runtime podman é estático (não exige podman instalado): todo serviço com
# `user:` coberto por userns keep-id, label=disable nos bind mounts e postura de portas/rede
# inalterada. Um serviço novo esquecido no override vira vermelho aqui, não `Permission denied`.
run "podman-config" 'bash tools/podman-check.sh'
# O estado do bucket é um contrato, não uma configuração: versionamento ligado (o direito
# de desfazer delete/overwrite) e PublicAccessBlock aplicado (o bucket é privado — o app
# serve a mídia por stream). O bootstrap é idempotente e falha alto se o storage não
# conseguir chegar nesse estado (D7).
run "storage-bucket" 'dc exec -T php-fpm php artisan storage:ensure-bucket --no-interaction'
# O backup da mídia (D7) tem que continuar sendo executável: o dry-run lista o que seria
# copiado para o bucket offsite, sem escrever. É barato (só a listagem) e pega o comando
# quebrado antes do dia em que ele seria preciso.
run "media-backup" 'dc exec -T php-fpm php artisan media:backup --dry-run --no-interaction'
run "security-check" "SECURITY_EDGE_URL='http://web:8080' SECURITY_PORTAL_HOST='$(portal_host)' SECURITY_CMS_HOST='cms.$(portal_host)' dc exec -T -e SECURITY_EDGE_URL -e SECURITY_PORTAL_HOST -e SECURITY_CMS_HOST php-fpm bash tools/security-check.sh"
if upload_chain_stale; then
    run "upload-chain" "dc exec -T php-fpm bash tools/upload-chain-check.sh && mkdir -p storage/framework && touch $UPLOAD_CHAIN_STAMP"
else
    skip "upload-chain" "cadeia de timeout inalterada desde o último verde"
fi
run "optimize-check" 'dc exec -T php-fpm php tools/optimize-check'
if optimize_stale; then
    run "optimize-verify" "optimize_verify && mkdir -p storage/framework && touch $OPTIMIZE_STAMP"
else
    skip "optimize-verify" "config/routes/views sem mudança desde o último verde"
fi

# ── 3. Dependências (vulnerabilidades + SBOM) ─────────────────────────────────────────
run "audit" "dc exec -T php-fpm composer audit --locked && nod 'npm audit --audit-level=high'"
# SBOM CycloneDX determinístico (ADR-020): artefato do release, gerado no caminho único.
run "sbom" "dc exec -T php-fpm php tools/sbom.php > sbom.cdx.json"

# ── 4. Testes e cobertura (ADR-038) ───────────────────────────────────────────────────
run "coverage" 'coverage_run'
run "coverage-check" 'dc exec -T php-fpm php tools/coverage-check.php'

# ── 5. E2E, acessibilidade e performance (exigem o app no ar) ─────────────────────────
# e2e/perf são opt-in (`E2E=1`); a11y (sniffCSS, barato) roda sempre.
if online; then
    if [ "$E2E" = "1" ]; then
        run "e2e" 'tools/e2e.sh test'
    else
        skip "e2e" "opt-in — rode 'make check E2E=1' (obrigatório ao fechar o escopo)"
    fi
    run "a11y" "SNIFF_BASE_URL='$BASE_URL' SNIFF_A11Y_STRICT='$STRICT' a11y_gate"
    if [ "$E2E" = "1" ]; then
        run "perf" 'perf_run'
    else
        skip "perf" "opt-in junto do e2e ('make check E2E=1')"
    fi
else
    # Pedir e2e explicitamente com o app fora é **falha**, não skipp: um verde de `make check
    # E2E=1` que nunca rodou o e2e é pior que um vermelho explícito (achado F5.I2.T2).
    if [ "$E2E" = "1" ]; then
        fail_check "e2e" "app offline em $BASE_URL — rode 'make up' (ou defina BASE_URL)"
    else
        skip "e2e" "app offline ($BASE_URL) — rode 'make up'"
    fi
    skip "a11y" "app offline ($BASE_URL)"
    skip "perf" "app offline ($BASE_URL)"
fi

# ── Relatório acumulado ───────────────────────────────────────────────────────────────
printf '\n\n%s========== RELATÓRIO ACUMULADO (make check) ==========%s\n' "$BOLD" "$RST"
printf '%-22s %-6s %s\n' "CHECK" "STATUS" "TEMPO"
printf '%s\n' "------------------------------------------------------"
for i in "${!names[@]}"; do
    printf '%-22s %-6s %s\n' "${names[$i]}" "${statuses[$i]}" "${durations[$i]}"
done

ok=0; failed=0; skipped=0
for s in "${statuses[@]}"; do
    case "$s" in
        OK) ok=$((ok + 1)) ;;
        FAIL) failed=$((failed + 1)) ;;
        SKIP) skipped=$((skipped + 1)) ;;
    esac
done

if [ "$failed" -gt 0 ]; then
    printf '\n%s========== DETALHES DAS FALHAS ==========%s\n' "$RED" "$RST"
    for i in "${!names[@]}"; do
        if [ "${statuses[$i]}" = "FAIL" ]; then
            printf '\n%s----- %s -----%s\n' "$BOLD" "${names[$i]}" "$RST"
            tail -n 40 "${logs[$i]}"
        fi
    done
fi

printf '\n%sTotal: %d OK · %d FAIL · %d SKIP%s (logs completos: %s)\n' \
    "$BOLD" "$ok" "$failed" "$skipped" "$RST" "$LOGDIR"

if [ "$E2E" != "1" ]; then
    printf '%s⊘ e2e/perf pulados (opt-in): rode `make check E2E=1` ao fechar o escopo.\n%s' "$YEL" "$RST"
fi

exit "$fail"
