#!/bin/sh
#
# Coleta, SOMENTE LEITURA, o que só o servidor sabe responder.
#
#   sh scripts/coletar-producao.sh > coleta.txt
#
# Não altera nada: nenhum UPDATE, nenhum arquivo movido, nenhum serviço tocado.
# A saída é feita para ser colada de volta com segurança — token de cron,
# senha e endereço IP saem redigidos, e as consultas devolvem CONTAGEM, nunca
# linha de dado pessoal.
#
# Rode no servidor de PRODUÇÃO. As consultas de duplicata podem rodar na
# réplica, se preferir poupar o primário: aponte com REPLICA=1 e ajuste
# DB_HOST.
#
# As credenciais são lidas de config/autoload/local.php, para não ficarem no
# histórico do shell.

set -u

LOGS_NGINX="${LOGS_NGINX:-/var/log/nginx}"

# Localiza a raiz do projeto pelo diretório de configuração, que existe em
# qualquer instalação Laminas — e NÃO por um arquivo específico dentro dele.
#
# A primeira versão procurava `config/autoload/local.php`, e isso estava errado:
# esse arquivo é criação da W1, e a W1 ainda não foi implantada. Em produção as
# credenciais continuam onde sempre estiveram, dentro dos `*.global.php`.
localizar_raiz() {
    for candidato in \
        "${RAIZ:-}" \
        "$(pwd)" \
        "$(cd "$(dirname "$0")/.." 2>/dev/null && pwd)" \
        /var/www/franquiabv.com.br/public_html \
        /var/www/html \
        /home/*/public_html
    do
        [ -n "$candidato" ] || continue

        if [ -d "$candidato/config/autoload" ] && [ -d "$candidato/module" ]; then
            echo "$candidato"
            return 0
        fi
    done

    return 1
}

RAIZ="$(localizar_raiz)" || {
    echo "ERRO: não encontrei a raiz do projeto." >&2
    echo "" >&2
    echo "Procuro por um diretório que tenha config/autoload/ e module/." >&2
    echo "" >&2
    echo "Informe o caminho:" >&2
    echo "    RAIZ=/caminho/do/projeto sh $0" >&2
    echo "" >&2
    echo "Para descobrir onde está:" >&2
    echo "    find / -type d -path '*/config/autoload' 2>/dev/null" >&2
    exit 1
}

# Extrai as credenciais MESCLANDO todos os config/autoload/*.php, que é
# exatamente o que o Laminas faz ao subir. Assim funciona tanto antes do deploy
# da W1 (credenciais nos *.global.php) quanto depois (em local.php), sem o
# script precisar saber em qual arquivo elas estão.
#
# Os valores nunca são impressos — só o NOME do arquivo que os forneceu.
eval "$(php -r '
$dir = $argv[1] . "/config/autoload";
$merged = [];
$origem = "";

foreach (glob("$dir/*.php") as $arquivo) {
    $c = @include $arquivo;

    if (!is_array($c)) {
        continue;
    }

    $p = $c["doctrine"]["connection"]["orm_default"]["params"] ?? null;

    if (is_array($p)) {
        // Quem vier depois sobrescreve, como no merge do Laminas.
        $merged = array_merge($merged, array_filter($p, fn($v) => $v !== null && $v !== ""));

        if (isset($p["dbname"]) && $p["dbname"] !== "") {
            $origem = basename($arquivo);
        }
    }
}

printf("DB_HOST=%s\nDB_PORT=%s\nDB_NAME=%s\nDB_USER=%s\nDB_PASS=%s\nDB_ORIGEM=%s\n",
    escapeshellarg($merged["host"] ?? "localhost"),
    escapeshellarg($merged["port"] ?? "3306"),
    escapeshellarg($merged["dbname"] ?? ""),
    escapeshellarg($merged["user"] ?? ""),
    escapeshellarg($merged["password"] ?? ""),
    escapeshellarg($origem !== "" ? $origem : "NAO ENCONTRADO"));
' "$RAIZ")"

mysql_q() {
    mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
        -t -e "$1" 2>/dev/null | grep -v "Using a password"
}

titulo() { echo ""; echo "=== $1"; echo ""; }

echo "Coleta de produção — $(date '+%d/%m/%Y %H:%M:%S')"
echo "Somente leitura. Token, senha e IP redigidos."
echo "Projeto:   $RAIZ"
echo "Servidor:  $(hostname)"
echo "Banco:     $DB_NAME  (credenciais lidas de $DB_ORIGEM)"
echo ""
echo "Arquivos em config/autoload:"
ls -1 "$RAIZ/config/autoload" 2>/dev/null | sed 's/^/  /'

if [ -z "$DB_NAME" ]; then
    echo ""
    echo "AVISO: não achei as credenciais do banco em nenhum config/autoload/*.php."
    echo "       As seções 1 a 4 sairão vazias. Informe manualmente com:"
    echo "         DB_HOST=... DB_NAME=... DB_USER=... DB_PASS=... sh $0"
fi

if ! command -v mysql >/dev/null 2>&1; then
    echo ""
    echo "AVISO: o cliente 'mysql' não está instalado nesta máquina."
    echo "       As seções 1 a 4 sairão vazias. Rode num servidor que tenha o"
    echo "       cliente, ou instale com: apt install default-mysql-client"
fi

# --------------------------------------------------------------------------
titulo "1. Versão e configuração do banco"
# Confirma o que até agora foi inferido: versão exata, se ONLY_FULL_GROUP_BY
# está mesmo desligado, e se o agendador de eventos está ligado (um EVENT
# habilitado num servidor com o agendador desligado nunca rodou — muda o que
# a F1.6 precisa migrar).

mysql_q "SELECT VERSION() AS versao, @@sql_mode AS sql_mode, @@event_scheduler AS event_scheduler\G"

# --------------------------------------------------------------------------
titulo "2. Duplicatas — decide o D29 (restrições de unicidade)"
# Se houver duplicata, o CREATE UNIQUE INDEX falha durante a migração. A base
# anonimizada não responde isto: ela deriva estas quatro colunas do id.

mysql_q "
SELECT 'usuarios.login' AS onde, COUNT(*) AS valores_repetidos, COALESCE(SUM(n),0) AS linhas
  FROM (SELECT login, COUNT(*) n FROM usuarios
         WHERE login IS NOT NULL AND login <> '' GROUP BY login HAVING n > 1) a
UNION ALL SELECT 'usuarios.email', COUNT(*), COALESCE(SUM(n),0)
  FROM (SELECT email, COUNT(*) n FROM usuarios
         WHERE email IS NOT NULL AND email <> '' GROUP BY email HAVING n > 1) b
UNION ALL SELECT 'clientes.cpf', COUNT(*), COALESCE(SUM(n),0)
  FROM (SELECT cpf, COUNT(*) n FROM clientes
         WHERE cpf IS NOT NULL AND cpf <> '' GROUP BY cpf HAVING n > 1) c
UNION ALL SELECT 'unidades.hash', COUNT(*), COALESCE(SUM(n),0)
  FROM (SELECT hash, COUNT(*) n FROM unidades
         WHERE hash IS NOT NULL AND hash <> '' GROUP BY hash HAVING n > 1) d;"

# --------------------------------------------------------------------------
titulo "3. Telefone e CPF — decide a chave de identidade"

mysql_q "
SELECT COUNT(*) AS total,
       SUM(cpf IS NOT NULL AND cpf <> '')                          AS com_cpf,
       SUM(telefone_sem_mascara IS NULL OR telefone_sem_mascara='') AS sem_telefone,
       SUM(LENGTH(telefone_sem_mascara) = 11)                       AS celular_9_digitos,
       SUM(LENGTH(telefone_sem_mascara) = 10
           AND SUBSTRING(telefone_sem_mascara,3,1) IN ('6','7','8','9')) AS celular_8_digitos,
       SUM(LENGTH(telefone_sem_mascara) = 10
           AND SUBSTRING(telefone_sem_mascara,3,1) IN ('2','3','4','5')) AS fixo
  FROM clientes;"

titulo "3b. Telefone compartilhado — dimensiona o caso do telefone de família"

mysql_q "
SELECT clientes_no_mesmo_numero, COUNT(*) AS quantidade_de_numeros
  FROM (SELECT telefone_sem_mascara, COUNT(*) AS clientes_no_mesmo_numero
          FROM clientes
         WHERE telefone_sem_mascara IS NOT NULL AND telefone_sem_mascara <> ''
         GROUP BY telefone_sem_mascara
        HAVING clientes_no_mesmo_numero > 1) x
 GROUP BY clientes_no_mesmo_numero
 ORDER BY clientes_no_mesmo_numero DESC
 LIMIT 10;"

# --------------------------------------------------------------------------
titulo "4. Datas que o PostgreSQL rejeita — confirma o alcance da limpeza"

mysql_q "
SELECT 'agendas.data' AS coluna, COUNT(*) AS invalidas FROM agendas WHERE data < '1000-01-01'
UNION ALL SELECT 'agendas_fechamentos.data_inicio', COUNT(*) FROM agendas_fechamentos WHERE data_inicio < '1000-01-01'
UNION ALL SELECT 'agendas_fechamentos.data_fim',    COUNT(*) FROM agendas_fechamentos WHERE data_fim    < '1000-01-01'
UNION ALL SELECT 'usuarios.data_contratacao',       COUNT(*) FROM usuarios WHERE data_contratacao < '1000-01-01';"

# --------------------------------------------------------------------------
titulo "5. Crontab — o que está REALMENTE agendado"
# O token viaja na query string das rotas de cron, então sai redigido.
#
# E NO CABEÇALHO TAMBÉM, desde 03/10/2026 (D4): as rotinas aceitam
# `Authorization: Bearer <token>`, e a linha de crontab que o tira da URL
# passa a trazê-lo como `-H "Authorization: Bearer ..."`. Redigir só a query
# imprimiria esse token em claro na coleta.

# Sem repetir usuário: $(whoami) costuma ser root, e a primeira versão
# imprimia o crontab de root duas vezes.
vistos=""

for usuario in root www-data "$(whoami)"; do
    case " $vistos " in *" $usuario "*) continue ;; esac
    vistos="$vistos $usuario"

    saida=$(crontab -l -u "$usuario" 2>/dev/null)

    if [ -n "$saida" ]; then
        echo "--- crontab de $usuario ---"
        echo "$saida" | sed -E \
            -e 's/([?&](token|senha|password|key|hash)=)[^&"'"'"' ]*/\1REDIGIDO/gi' \
            -e 's/(Bearer[[:space:]]+)[^"'"'"'[:space:]]+/\1REDIGIDO/gi'
    fi
done

if [ -d /etc/cron.d ]; then
    echo "--- /etc/cron.d ---"
    grep -rhE "^[^#]" /etc/cron.d 2>/dev/null \
        | sed -E \
            -e 's/([?&](token|senha|password|key|hash)=)[^&"'"'"' ]*/\1REDIGIDO/gi' \
            -e 's/(Bearer[[:space:]]+)[^"'"'"'[:space:]]+/\1REDIGIDO/gi'
fi

# --------------------------------------------------------------------------
titulo "6. Versões do aplicativo em uso — decide o que pode ser aposentado"
# Só agregado: nada de IP nem de user-agent completo.

if [ -d "$LOGS_NGINX" ]; then
    # Quais arquivos de log existem de fato.
    #
    # A primeira versão procurava só em `access.log*`, e a coleta de 19/08
    # devolveu ZERO requisições a /api/versao/app — impossível, já que aquele
    # endpoint é chamado a cada abertura do aplicativo. O zero não era ausência
    # de tráfego: era o site ter log próprio por vhost, fora daquele glob.
    echo "arquivos de log encontrados:"
    ls -1 "$LOGS_NGINX"/ 2>/dev/null | sed 's/^/  /'
    echo ""

    # Daqui para baixo, varre TODOS os logs do diretório, comprimidos ou não.
    varrer() {
        { zgrep -hE "$1" "$LOGS_NGINX"/*.log "$LOGS_NGINX"/*.log.* 2>/dev/null; } \
        || { grep -hE "$1" "$LOGS_NGINX"/*.log 2>/dev/null; }
    }

    echo "rotas do aplicativo mais chamadas:"
    varrer "/api/(agendas|clientes?|banners|versao)/" \
    | grep -oE '/api/(agendas|clientes?|banners|versao)/[a-zA-Z0-9]+' \
    | sort | uniq -c | sort -rn | head -25 | sed 's/^/  /'

    echo ""
    echo "GERACAO DO APLICATIVO — rotas versionadas (D5):"
    # Este e o sinal que identifica qual geracao do aplicativo esta em campo.
    #
    # O User-Agent NAO serve para isso: CFNetwork, okhttp e Dart sao versoes da
    # biblioteca HTTP do iOS, do Android e do Flutter — dizem plataforma, nao
    # versao do app.
    #
    # Ja a rota diz: quem chama /api/v3/agendas/agendar roda a geracao 3, quem
    # chama /api/agendas/agendar sem prefixo roda a mais antiga. E o que decide
    # qual contrato pode ser aposentado.
    for rota in         "/api/agendas/agendar"         "/api/v2/agendas/agendar"         "/api/v3/agendas/agendar"         "/api/agendas/getAgenda"         "/api/v2/agendas/getAgenda"         "/api/agendas/getUnidade/"         "/api/v2/agendas/getUnidade/"
    do
        printf "  %-32s %s
" "$rota" "$(varrer " ${rota}" | wc -l)"
    done

    echo ""
    echo "plataforma do cliente (User-Agent — diz o SO, nao a versao do app):"
    varrer "/api/(agendas|clientes?)/" \
    | grep -oE '(okhttp/[0-9.]+|Dart/[0-9.]+|CFNetwork/[0-9.]+|Alamofire/[0-9.]+|Expo/[0-9.]+|Mozilla/[0-9.]+)' \
    | sort | uniq -c | sort -rn | head -12 | sed 's/^/  /'

    echo ""
    echo "alguém busca /app.json direto pela URL?"
    # Decide se aquele arquivo pode sair do disco (D36). A API expõe a versão em
    # /api/versao/app, mas o aplicativo pode estar buscando o arquivo cru — e há
    # versões antigas em campo que não dá para atualizar (D5).
    #
    # Os dois números importam JUNTOS: se ambos derem zero, a medição não vale,
    # é o log errado. Só faz sentido concluir se o do endpoint for maior que zero.
    printf "  requisições diretas a /app.json:  %s\n" "$(varrer 'GET /app.json' | wc -l)"
    printf "  requisições a /api/versao/app:    %s\n" "$(varrer '/api/versao/app' | wc -l)"
    printf "  requisições a QUALQUER /api/:     %s\n" "$(varrer ' /api/' | wc -l)"

    echo ""
    echo "período coberto pelos logs:"
    ls -1 "$LOGS_NGINX"/access.log* 2>/dev/null | head -12 | while read -r f; do
        printf "  %-34s %s\n" "$(basename "$f")" \
            "$(date -r "$f" '+%Y-%m-%d %H:%M' 2>/dev/null)"
    done
else
    echo "$LOGS_NGINX não acessível — rode como root."
fi

# --------------------------------------------------------------------------
titulo "7. Estado do Dia 0 — o que ainda está exposto"

for alvo in clientes.txt phpinfofile.php log_scripts.txt logs; do
    if [ -e "$RAIZ/public/$alvo" ]; then
        echo "  PRESENTE  public/$alvo"
    else
        echo "  ausente   public/$alvo"
    fi
done

echo ""
if grep -rqE "migrar-senhas|migrarSenhas" "$RAIZ/module" "$RAIZ/config" 2>/dev/null; then
    echo "  ROTA migrar-senhas PRESENTE no código implantado"
else
    echo "  rota migrar-senhas ausente"
fi

# --------------------------------------------------------------------------
titulo "8. Uploads no disco — dimensiona a F1.5"

for d in arquivos contratos banners assistentes vip-class layout/img/avatar; do
    if [ -d "$RAIZ/public/$d" ]; then
        printf "  %-24s %8s arquivos  %8s\n" "$d" \
            "$(find "$RAIZ/public/$d" -type f 2>/dev/null | wc -l)" \
            "$(du -sh "$RAIZ/public/$d" 2>/dev/null | cut -f1)"
    fi
done

echo ""
echo "=== fim da coleta ==="
