<?php
/**
 * scripts/backfill_bsuid.php
 *
 * Rellena users.bsuid a partir del histórico de webhook_logs.
 *
 * Meta manda el BSUID en todos los webhooks de mensaje, así que en el histórico
 * están las equivalencias BSUID↔teléfono de la gente que escribió cuando aún
 * mostraba su número. Cargarlas significa que, el día que oculten el teléfono,
 * el bot siga sabiendo quiénes son en vez de tratarlos como desconocidos.
 *
 * Es idempotente: se puede correr las veces que haga falta.
 *
 * La lectura del histórico tarda más de media hora (son ~230.000 registros de
 * un servidor remoto), y mantener la conexión abierta todo ese rato la mataba
 * antes de empezar a escribir: "MySQL server has gone away". Por eso el mapa se
 * guarda en disco y la escritura usa una conexión nueva. De paso, reintentar
 * sale gratis: si el mapa ya está en caché, no se vuelve a leer la tabla.
 *
 * Uso:
 *   php scripts/backfill_bsuid.php --simular   (no escribe, solo informa)
 *   php scripts/backfill_bsuid.php
 *   php scripts/backfill_bsuid.php --releer    (ignora la caché y relee el histórico)
 */

require_once __DIR__ . '/../config/config.php';

$simular = in_array('--simular', $argv, true);
$releer  = in_array('--releer',  $argv, true);
echo $simular ? "Modo simulación: no se escribe nada.\n\n" : "Aplicando cambios.\n\n";

$cache = sys_get_temp_dir() . '/backfill_bsuid_mapa.json';

// --- 1. Recorrer el histórico y armar el mapa BSUID → teléfono ---
$mapa = [];
$sinTelefono = [];

if (!$releer && is_readable($cache)) {
    $guardado    = json_decode(file_get_contents($cache), true) ?: [];
    $mapa        = $guardado['mapa'] ?? [];
    $sinTelefono = $guardado['sinTelefono'] ?? [];
    printf("Mapa tomado de la caché (%s, del %s).\n", basename($cache), date('Y-m-d H:i', filemtime($cache)));
    printf("Para releer el histórico: --releer\n\n");
}

if (!$mapa) {
echo "Leyendo webhook_logs (tarda; es una sola vez)...\n";
$db = Database::getInstance();

// Una sola pasada sin buffer: son más de 400.000 registros, así que ni caben
// en memoria de golpe ni conviene reconsultar la tabla por bloques (cada bloque
// volvía a recorrerla entera y tardaba una eternidad).
$conn = $db->getConnection();
$bufferPrevio = $conn->getAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY);
$conn->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

$leidos = 0;

try {
    $stmt = $conn->query(
        "SELECT request_body FROM webhook_logs
         WHERE request_body LIKE '%user_id%' AND request_body LIKE '%\"messages\"%'"
    );

    while ($fila = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $leidos++;
        if ($leidos % 50000 === 0) printf("  ...%d registros\n", $leidos);
        $d = json_decode($fila['request_body'], true);
        if (!$d) continue;

    foreach ($d['entry'] ?? [] as $entry) {
        foreach ($entry['changes'] ?? [] as $cambio) {
            $v = $cambio['value'] ?? [];

            // El teléfono puede venir en contacts[].wa_id o en messages[].from
            foreach ($v['contacts'] ?? [] as $c) {
                if (!empty($c['user_id']) && !empty($c['wa_id'])) {
                    $mapa[$c['user_id']] = $c['wa_id'];
                }
            }
            foreach ($v['messages'] ?? [] as $m) {
                $uid = $m['from_user_id'] ?? null;
                if (!$uid) continue;
                if (!empty($m['from'])) $mapa[$uid] = $m['from'];
                else                    $sinTelefono[$uid] = true;
            }

            // Cuando alguien comparte su contacto, el teléfono llega aquí
            foreach ($v['messages'] ?? [] as $m) {
                if (($m['type'] ?? '') !== 'contacts') continue;
                $uid = $m['from_user_id'] ?? null;
                foreach ($m['contacts'] ?? [] as $c) {
                    foreach ($c['phones'] ?? [] as $t) {
                        $num = $t['wa_id'] ?? ($t['phone'] ?? null);
                        if ($uid && $num) $mapa[$uid] = preg_replace('/[^0-9]/', '', $num);
                    }
                }
            }
        }
    }
    }
    $stmt->closeCursor();
} finally {
    // Dejar la conexión como estaba
    $conn->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $bufferPrevio);
}

printf("\n  registros revisados: %d\n", $leidos);
printf("  equivalencias encontradas: %d\n", count($mapa));
file_put_contents($cache, json_encode(['mapa' => $mapa, 'sinTelefono' => $sinTelefono]));
printf("  mapa guardado en %s\n", $cache);

// Se termina aquí a propósito. Tras media hora de lectura el servidor ya cerró
// esta conexión, y el singleton de Database no sabe reconectar: escribir ahora
// falla con "MySQL server has gone away" a mitad del recorrido, que es justo lo
// que pasó la primera vez. La segunda ejecución toma el mapa de la caché en un
// segundo y escribe con la conexión sana.
echo "\nMapa listo. Ejecute otra vez el comando para aplicarlo.\n";
exit(0);
}

printf("  equivalencias encontradas: %d\n", count($mapa));
printf("  personas que llegaron sin teléfono: %d\n", count($sinTelefono));

// --- 1b. Un solo BSUID por teléfono ---
// Hay números que aparecen con dos identificadores: la línea cambió de dueño
// (reciclada) o la persona se registró de nuevo. Si se recorre el mapa tal cual,
// ambos escriben sobre el mismo usuario y el que queda depende del orden: cada
// ejecución dejaba un valor distinto. Se resuelve antes de tocar la base,
// quedándose con el visto más tarde, y se escribe una sola vez por teléfono.
$porTelefono = [];
$elegido     = [];
foreach ($mapa as $b => $t) {
    $porTelefono[$t][] = $b;
    $elegido[$t] = $b;   // el orden del mapa es el de lectura: el último gana
}
$ambiguos = array_filter($porTelefono, fn($bs) => count($bs) > 1);
printf("  teléfonos distintos: %d\n", count($porTelefono));
printf("  teléfonos con más de un BSUID: %d (se toma el más reciente)\n\n", count($ambiguos));
foreach (array_slice($ambiguos, 0, 10, true) as $t => $bs) {
    printf("    %s → %s\n", substr($t, 0, -4) . '****', implode(', ', $bs));
}
if (count($ambiguos) > 10) printf("    (y %d más)\n", count($ambiguos) - 10);
if ($ambiguos) echo "\n";

// --- 2. Escribir el BSUID en el usuario que corresponde ---
$db = Database::getInstance();

$marcados = 0; $noExisten = 0; $yaEstaban = 0; $conflictos = 0;

foreach ($elegido as $telefono => $bsuid) {
    $u = $db->fetch("SELECT id, bsuid FROM users WHERE phone_number = :t", ['t' => $telefono]);
    if (!$u) { $noExisten++; continue; }
    if ($u['bsuid'] === $bsuid) { $yaEstaban++; continue; }
    if (!empty($u['bsuid'])) {
        printf("  aviso: %s tenía %s y queda con %s\n", $telefono, $u['bsuid'], $bsuid);
        $conflictos++;
    }
    if (!$simular) {
        try {
            $db->update('users', ['bsuid' => $bsuid], 'id = ?', [$u['id']]);
        } catch (Exception $e) {
            printf("  error en %s: %s\n", $telefono, $e->getMessage());
            continue;
        }
    }
    $marcados++;
}

printf("\n  usuarios marcados con su BSUID: %d\n", $marcados);
printf("  ya lo tenían: %d\n", $yaEstaban);
printf("  sin usuario en la base: %d\n", $noExisten);
printf("  BSUID reemplazados (cambio de número): %d\n", $conflictos);

// --- 3. ¿A cuántos de los perdidos rescatamos? ---
$rescatables = array_intersect_key($mapa, $sinTelefono);
printf("\n  de los que llegaron sin teléfono, quedan identificados: %d\n", count($rescatables));
foreach ($rescatables as $b => $t) {
    printf("    %-24s → %s\n", $b, substr($t, 0, -4) . '****');
}

$pendientes = array_diff_key($sinTelefono, $mapa);
printf("\n  siguen sin identificar: %d (hay que pedirles el contacto)\n", count($pendientes));

echo $simular ? "\nSimulación terminada, no se escribió nada.\n" : "\nListo.\n";
