#!/usr/bin/env bash
#
# tools/sniff-gate.sh — gate visual/acessibilidade via sniff (ADR-025, plano 11).
#
# Captura o snapshot COMPLETO da página, roda os checks derivados (contraste,
# alvo 24px, foco, alt, uniformidade) e falha conforme a severidade:
#   - sempre falha se houver `fail` (violação medida, ex.: contraste < AA);
#   - com SNIFF_A11Y_STRICT=1 também falha em qualquer `warn` (ex.: contraste
#     "unmeasurable" ou `transition: all`).
#
# Antes de capturar, exige **HTTP 200** no alvo: sem isso o gate daria verde
# sobre uma página de erro (404/500) — o "verde falso" mais caro de todos.
#
# Exceções documentadas (limitações **medidas** do sniffCSS, nunca silenciadas em massa):
#  1) `contrast-aa|aaa` **não mensurável** (`transparent background` / `background image`): o sniff
#     mede só o fundo do **próprio** elemento — não compõe ancestrais nem resolve imagem de fundo.
#     O contraste é arbitrado pelo **axe** (`make e2e`), que resolve o fundo efetivo. Contraste
#     **medido** como insuficiente continua sendo `fail` e derruba o gate.
#  2) `text-not-selectable` dentro de controle (`button`/`.btn`): `user-select: none` é padrão
#     do daisyUI em rótulos de botão; texto corrido segue coberto.
# Qualquer outro `warn` derruba o modo estrito; `fail` derruba sempre.
#
# Uso: [SNIFF_BASE_URL=... KEY_PAGES="/ /dados-abertos" SNIFF_SELECTOR=... SNIFF_DEPTH=... SNIFF_A11Y_STRICT=1] tools/sniff-gate.sh
set -Eeuo pipefail

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

# Resumo do `sniff check` -> SUMMARY_PASS/WARN/FAIL. Falha (rc=1) quando a linha
# `rules: N pass | N warn | N fail` está ausente ou quando nada foi medido: sem resumo o gate
# **não pode** aprovar (PROTOCOLO §10.1: pular/falhar, nunca aprovar).
parse_summary() {
    local out="$1" line

    line="$(printf '%s\n' "$out" | grep -E 'rules: [0-9]+ pass \| [0-9]+ warn \| [0-9]+ fail' | tail -1 || true)"
    [ -n "$line" ] || return 1

    SUMMARY_PASS="$(printf '%s' "$line" | sed -nE 's/.*rules: ([0-9]+) pass.*/\1/p')"
    SUMMARY_WARN="$(printf '%s' "$line" | sed -nE 's/.*\| ([0-9]+) warn \|.*/\1/p')"
    SUMMARY_FAIL="$(printf '%s' "$line" | sed -nE 's/.*\| ([0-9]+) fail.*/\1/p')"

    [ -n "$SUMMARY_PASS" ] && [ -n "$SUMMARY_WARN" ] && [ -n "$SUMMARY_FAIL" ] || return 1
    [ $((SUMMARY_PASS + SUMMARY_WARN + SUMMARY_FAIL)) -gt 0 ] || return 1

    return 0
}

# Autoteste offline do parser (usado por `tools/gates-selftest.sh`): lê a saída do `sniff check`
# do stdin e replica a decisão do gate **sem** navegador/rede.
if [ "${1:-}" = "--verify-summary" ]; then
    out="$(cat)"

    if ! parse_summary "$out"; then
        echo "sniff-gate: FALHOU — resumo de regras ausente ou vazio; medição inválida." >&2
        exit 1
    fi

    echo "sniff-gate: resumo OK (pass=$SUMMARY_PASS warn=$SUMMARY_WARN fail=$SUMMARY_FAIL)"
    exit 0
fi

# Valor de uma chave do `.env` (sem aspas) com fallback: o compose publica o app em `WEB_PORT`.
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}"
}

# `localhost` (não 127.0.0.1): o app roteia por Host (domínios `localhost`/`cms.localhost`).
URL="${SNIFF_BASE_URL:-http://localhost:$(env_value WEB_PORT 50700)}"
SEL="${SNIFF_SELECTOR:-body}"
DEPTH="${SNIFF_DEPTH:-6}"
STRICT="${SNIFF_A11Y_STRICT:-0}"
SNIFF_GATE_IGNORE="${SNIFF_GATE_IGNORE:-contrast-(aa|aaa).*contrast unmeasurable|text-not-selectable.*(button|btn)}"

CURRENT_DIR="$ROOT/src/tests-e2e/sniff/current"
mkdir -p "$CURRENT_DIR"
# Retenção determinística por **nome** (timestamp UTC), nunca por mtime: mantém as N mais recentes.
SNAPSHOT_KEEP="${SNAPSHOT_KEEP:-10}"

# `KEY_PAGES` é uma lista de caminhos (espaço) medida na mesma passada; vazio = só a URL base.
KEY_PAGES="${SNIFF_KEY_PAGES:-${KEY_PAGES:-}}"
targets=()
if [ -z "$KEY_PAGES" ]; then
    targets=("$URL")
else
    for page in $KEY_PAGES; do
        case "$page" in
            http://*|https://*) targets+=("$page") ;;
            *) targets+=("${URL%/}/${page#/}") ;;
        esac
    done
fi

body="$(mktemp)"
trap 'rm -f "$body"' EXIT

rc=0
for target in "${targets[@]}"; do
    slug="$(printf '%s' "$target" | sed -E 's#^[a-z]+://##; s#[^A-Za-z0-9._-]+#-#g; s#^-+|-+$##g')"
    [ -n "$slug" ] || slug=home
    SNAP="${SNIFF_SNAPSHOT:-$CURRENT_DIR/$(date -u +%Y%m%dT%H%M%SZ)-${slug}-gate.jsonl}"

    # ── Sanidade do alvo ─────────────────────────────────────────────────────────
    status="$(curl -sS --max-time 20 -o "$body" -w '%{http_code}' "$target" || echo 000)"
    title="$(grep -oE '<title>[^<]*</title>' "$body" | head -1 | sed -E 's:</?title>::g' || true)"

    echo "sniff-gate: alvo $target -> HTTP $status · ${title:-sem título}"

    if [ "$status" != "200" ]; then
        echo "sniff-gate: FALHOU — alvo não é uma página 200 (HTTP $status). Abortando antes de medir." >&2
        rc=1
        continue
    fi

    echo "sniff-gate: capturando [$SEL] depth=$DEPTH -> ${SNAP#"$ROOT"/}"
    # `sniff capture` pode travar no browser/CDP (observado com Chromium 155/156): sem prazo, o
    # `make check` fica preso para sempre. O timeout transforma a falha em gate vermelho explícito.
    if ! timeout "${SNIFF_CAPTURE_TIMEOUT:-120}" \
        "$ROOT/tools/sniff.sh" capture --url "$target" --selector "$SEL" --depth "$DEPTH" --no-summary >"$SNAP"; then
        echo "sniff-gate: FALHOU — capture falhou ou excedeu ${SNIFF_CAPTURE_TIMEOUT:-120}s." >&2
        rc=1
        continue
    fi

    if [ ! -s "$SNAP" ]; then
        echo "sniff-gate: FALHOU — snapshot vazio (0 bytes); nada foi medido." >&2
        rc=1
        continue
    fi

    out="$("$ROOT/tools/sniff.sh" check --input "$SNAP" --rules --uniform 2>&1)"
    printf '%s\n' "$out"

    if ! parse_summary "$out"; then
        echo "sniff-gate: FALHOU — resumo de regras ausente ou vazio; medição inválida (sem linha 'rules: N pass | N warn | N fail')." >&2
        rc=1
        continue
    fi

    pass="$SUMMARY_PASS"; warn="$SUMMARY_WARN"; fail="$SUMMARY_FAIL"

    # `warn` conhecido e documentado não bloqueia, mas é sempre contado e exibido (nunca em silêncio).
    ignored="$(printf '%s\n' "$out" | grep -E '"status":"warn"' | grep -cE "$SNIFF_GATE_IGNORE" || true)"
    effective=$((warn - ignored))

    blocking="$(printf '%s\n' "$out" | grep -E '"status":"warn"' | grep -vE "$SNIFF_GATE_IGNORE" || true)"

    echo "sniff-gate: url=$target status=$status pass=$pass warn=$warn ignored=$ignored fail=$fail strict=$STRICT"

    if [ "$fail" -gt 0 ]; then
        echo "sniff-gate: FALHOU ($fail violação(ões) medida(s))." >&2
        rc=1
        continue
    fi

    if [ "$effective" -gt 0 ]; then
        printf '%s\n' "$blocking" >&2

        if [ "$STRICT" = "1" ]; then
            echo "sniff-gate: FALHOU em modo estrito ($effective aviso(s) não isento(s) — SNIFF_A11Y_STRICT=1)." >&2
            rc=1
            continue
        fi
    fi

    echo "sniff-gate: OK ($target)"
done

# Poda só quando o snapshot foi para o diretório padrão (respeita `SNIFF_SNAPSHOT` custom).
if [ -z "${SNIFF_SNAPSHOT:-}" ]; then
    case "$SNAPSHOT_KEEP" in
        ''|*[!0-9]*) echo "sniff-gate: SNAPSHOT_KEEP inválido ('$SNAPSHOT_KEEP') — mantendo 10." >&2; SNAPSHOT_KEEP=10 ;;
    esac
    ls -1 "$CURRENT_DIR"/*-gate.jsonl 2>/dev/null | sort -r | tail -n +$((SNAPSHOT_KEEP + 1)) | while IFS= read -r old; do rm -f "$old"; done
fi

exit "$rc"
