#!/usr/bin/env bash
#
# tools/upload-chain-check.sh — prova de ponta a ponta da cadeia de timeout do upload (D0).
#
# O defeito medido: o Caddy cortava o upload em ~10 s (`read_body 10s`) e o `write 30s`
# cortava a resposta, tornando **inalcançável** o limite de 110 MiB declarado em três
# camadas (ADR-026). Subir um prazo sem os outros troca o sintoma (504 → HTTP 100) sem
# resolver — por isso a prova cobre os dois sentidos da cadeia.
#
# O que este script prova:
#   1. um upload **lento** (100 MiB a 2 MB/s) atravessa o edge e chega ao app inteiro,
#      sem 504 (prazo do edge) e sem HTTP 100 (o cliente desistiu no meio);
#   2. um download **lento** (100 MiB a 2 MB/s) sai inteiro, sem 502/504.
#
# O status de resposta do app **não** precisa ser 200: a rota de prova é pública e sem
# token CSRF, então o Laravel responde 419 — o que interessa é o *tempo* (o corpo foi
# transferido inteiro na taxa imposta) e o código (não é erro do edge).
#
# Não é teste unitário: é prova de runtime, por isso vive em `tools/` (e não em `tests/`).
# Custa ~2 min (100 MiB a 2 MB/s nos dois sentidos) ⇒ entra no `make check` como passo
# **gated por staleness** (só roda quando a cadeia de timeout muda).
#
# Uso:
#   tools/upload-chain-check.sh                        # edge em http://web:8080 (dentro do container)
#   EDGE_URL=http://localhost:8080 tools/upload-chain-check.sh
#   UPLOAD_MB=10 UPLOAD_RATE=1M tools/upload-chain-check.sh   # prova rápida
#   tools/upload-chain-check.sh --json
#
# Sai != 0 se o upload ou o download forem cortados pelo edge.
set -Eeuo pipefail

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

EDGE_URL="${EDGE_URL:-http://web:8080}"
UPLOAD_MB="${UPLOAD_MB:-100}"
DOWNLOAD_MB="${DOWNLOAD_MB:-100}"
RATE="${UPLOAD_RATE:-2M}"
# A rota de prova: pública, sem sessão e sem CSRF. Qualquer POST que **leia o corpo**
# serve; o que importa é que o corpo atravesse Caddy → Apache → PHP-FPM.
PROBE_PATH="${PROBE_PATH:-/lgpd}"
# Host confiável: um `Host` fora da lista de `trusted_hosts` faz o Symfony devolver o host
# **vazio** (proteção anti host-header-poisoning) ⇒ URL inválida ⇒ 500. O default vem do
# `APP_URL`, que é a mesma fonte de `trusted_hosts` (config/app.php).
HOST_HEADER="${HOST_HEADER:-$(grep -E '^APP_URL=' .env 2>/dev/null | tail -1 | cut -d= -f2- \
    | sed -E 's#^[a-z]+://([^/:]+).*#\1#')}"
JSON=0
[ "${1:-}" = "--json" ] && JSON=1

tmp="$(mktemp -d)"
trap 'rm -f "$tmp"/payload.bin "$tmp"/download.bin; rmdir "$tmp" 2>/dev/null || true' EXIT

# `rate_bytes`: "2M" ⇒ 2*1024*1024. É o teto teórico do tempo de transferência.
rate_bytes() {
    local r="${1,,}" n
    case "$r" in
        *k) n="${r%k}"; n=$((n * 1024)) ;;
        *m) n="${r%m}"; n=$((n * 1024 * 1024)) ;;
        *)  n="$r" ;;
    esac
    printf '%s' "$n"
}

RATE_BYTES="$(rate_bytes "$RATE")"
fail=0
result='{}'

log() { [ "$JSON" = "1" ] || printf '%s\n' "$*"; }

fail_msg() {
    printf 'upload-chain-check: %s\n' "$*" >&2
    fail=1
}

# ── 1. Upload lento ───────────────────────────────────────────────────────────────
# O payload é determinístico (zeros): o conteúdo não importa, o **tamanho** e a taxa sim.
head -c $((UPLOAD_MB * 1024 * 1024)) /dev/zero > "$tmp/payload.bin"

# Um único curl: código **e** tempo na mesma resposta (reenviar o corpo custaria 2x).
upload_meta="$(curl -sS -o /dev/null -w '%{http_code} %{time_total}' \
    --limit-rate "$RATE" --max-time 600 \
    -H "Host: $HOST_HEADER" \
    -F "arquivo=@$tmp/payload.bin" \
    "$EDGE_URL$PROBE_PATH" 2>/dev/null || echo '000 0')"
upload_code="${upload_meta%% *}"
upload_time="${upload_meta##* }"

expected_upload=$((UPLOAD_MB * 1024 * 1024 / RATE_BYTES))

case "$upload_code" in
    504) fail_msg "upload cortado pelo edge (504): a janela do Caddy não comporta ${UPLOAD_MB} MiB" ;;
    502) fail_msg "upload falhou no edge (502)" ;;
    100) fail_msg "upload interrompido (HTTP 100): o cliente desistiu antes do fim" ;;
    000) fail_msg "upload não chegou ao edge (000) — o edge está no ar?" ;;
    *)  log "upload: HTTP $upload_code em ${upload_time}s (esperado ≥ ${expected_upload}s)" ;;
esac

# O tempo é a prova de que o corpo foi **inteiro**: se o edge cortasse, a resposta viria
# em ~10 s (o prazo antigo) em vez de tempo/rate.
upload_time_int="${upload_time%%.*}"
[ -n "$upload_time_int" ] || upload_time_int=0
if [ "$upload_time_int" -lt $((expected_upload * 9 / 10)) ]; then
    fail_msg "upload voltou em ${upload_time_int}s, menos que os ${expected_upload}s esperados a ${RATE}: o corpo não foi transferido inteiro"
fi

# ── 2. Download lento ─────────────────────────────────────────────────────────────
# O arquivo é servido pelo próprio edge (docroot). `public/build` é gitignored ⇒ o
# probe nunca entra no repositório, e o `trap` o remove.
serve_path="public/build/upload-chain-probe.bin"
if [ ! -d public/build ]; then
    fail_msg 'public/build não existe (assets não compilados) — rode o build antes'
else
    head -c $((DOWNLOAD_MB * 1024 * 1024)) /dev/zero > "$ROOT/$serve_path"

    download_code="$(curl -sS -o "$tmp/download.bin" -w '%{http_code}' \
        --limit-rate "$RATE" --max-time 600 \
        -H "Host: $HOST_HEADER" \
        "$EDGE_URL/build/upload-chain-probe.bin" 2>/dev/null || echo 000)"
    download_size="$(stat -c %s "$tmp/download.bin" 2>/dev/null || echo 0)"
    expected_download=$((DOWNLOAD_MB * 1024 * 1024))

    case "$download_code" in
        504) fail_msg "download cortado pelo edge (504)" ;;
        502) fail_msg "download falhou no edge (502)" ;;
        200) ;;
        000) fail_msg "download não chegou ao edge (000)" ;;
        *)  fail_msg "download com status inesperado: $download_code" ;;
    esac

    if [ "$download_size" -ne "$expected_download" ]; then
        fail_msg "download truncado: ${download_size} bytes de ${expected_download}"
    else
        log "download: HTTP $download_code, ${download_size} bytes (íntegro)"
    fi

    rm -f "$ROOT/$serve_path"
fi

result=$(printf '{"upload":{"code":"%s","bytes":%s,"expected_seconds":%s},"download":{"bytes":%s}}' \
    "$upload_code" $((UPLOAD_MB * 1024 * 1024)) "$expected_upload" \
    "${download_size:-0}")

if [ "$JSON" = "1" ]; then
    printf '%s\n' "$result"
fi

if [ "$fail" -ne 0 ]; then
    printf 'upload-chain-check: FALHOU\n' >&2
    exit 1
fi

printf 'upload-chain-check: OK\n' >&2
exit 0
