<?php
// webhook_ultramsg.php – guarda contacto + mensaje + media (recibido y enviado)
// + Andrea (bot GPT) con config en tabla `bot` y consultas al catálogo `productos`.

require_once __DIR__ . '/config.php';

$BASE_MEDIA_DIR   = __DIR__ . '/media';
const ANDREA_LOG_FILE = __DIR__ . '/debug_andrea.log';

/* =========================================================
 * Helpers de logging
 * ========================================================= */

function andrea_log(string $msg): void
{
    $line = '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
    @file_put_contents(ANDREA_LOG_FILE, $line, FILE_APPEND);
}

/* =========================================================
 * Normalización teléfono + envío por UltraMsg
 * ========================================================= */

function andrea_normalizar_telefono(string $telefono): string
{
    $to = trim($telefono);
    $to = str_replace('@c.us', '', $to);

    if ($to !== '' && $to[0] !== '+') {
        $digits = preg_replace('/\D+/', '', $to);
        $digits = ltrim($digits, '0');

        if ($digits === '') {
            andrea_log("ERROR normalizando teléfono vacío desde: {$telefono}");
            return '';
        }

        if (strpos($digits, '549') === 0) {
            $to = '+' . $digits;
        } elseif (strpos($digits, '54') === 0) {
            $to = '+' . $digits;
        } elseif (strpos($digits, '341') === 0) {
            $to = '+549' . $digits;
        } else {
            $to = '+' . $digits;
        }
    }

    return $to;
}

function andrea_enviar_whatsapp(string $telefono, string $mensaje): bool
{
    if (!defined('ULTRA_INSTANCE_ID') || !defined('ULTRA_TOKEN')) {
        andrea_log("ERROR: ULTRA_INSTANCE_ID o ULTRA_TOKEN no definidos en config.php");
        return false;
    }

    $to = andrea_normalizar_telefono($telefono);
    if ($to === '') {
        return false;
    }

    $params = [
        'token'    => ULTRA_TOKEN,
        'to'       => $to,
        'body'     => $mensaje,
        'priority' => 10,
    ];

    $url = "https://api.ultramsg.com/" . ULTRA_INSTANCE_ID . "/messages/chat";

    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING       => "",
        CURLOPT_MAXREDIRS      => 10,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_SSL_VERIFYHOST => 0,
        CURLOPT_SSL_VERIFYPEER => 0,
        CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST  => "POST",
        CURLOPT_POSTFIELDS     => http_build_query($params),
        CURLOPT_HTTPHEADER     => ["content-type: application/x-www-form-urlencoded"],
    ]);

    $response = curl_exec($ch);
    $err      = curl_error($ch);
    curl_close($ch);

    andrea_log("SEND to={$to} body=" . json_encode($mensaje, JSON_UNESCAPED_UNICODE) . " resp={$response} err={$err}");

    if ($err) {
        return false;
    }

    $data = json_decode($response, true);
    if (is_array($data) && isset($data['sent'])) {
        return ($data['sent'] === 'true' || $data['sent'] === true || $data['sent'] === 1 || $data['sent'] === '1');
    }

    return true;
}

/* =========================================================
 * Helpers texto + despedidas
 * ========================================================= */

function andrea_normalize_text(string $text): string
{
    $text = trim($text);
    $text = mb_strtolower($text, 'UTF-8');
    return $text;
}

function andrea_classify_farewell(string $message): string
{
    $clean = andrea_normalize_text($message);

    $pureFarewells = [
        'hasta luego',
        'chau',
        'chao',
        'adios',
        'adiós',
        'nos vemos',
        'bye',
        'gracias',
        'muchas gracias',
    ];

    if (in_array($clean, $pureFarewells, true)) {
        if ($clean === 'hasta luego') {
            return 'silent';
        }
        return 'silent';
    }

    if (
        mb_strpos($clean, 'hasta luego') !== false ||
        mb_strpos($clean, 'nos vemos')   !== false ||
        mb_strpos($clean, 'gracias')     !== false ||
        mb_strpos($clean, 'adios')       !== false ||
        mb_strpos($clean, 'adiós')       !== false
    ) {
        return 'bye';
    }

    return 'none';
}

/* =========================================================
 * Config del bot desde DB
 * ========================================================= */

function andrea_get_bot_config(PDO $pdo): array
{
    $stmt = $pdo->prepare("SELECT * FROM bot WHERE id = 1 LIMIT 1");
    $stmt->execute();
    $bot = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$bot) {
        $defaultPrompt = "Eres Andrea, una asistente virtual amable, clara y breve de Netfan. "
            . "Respondés siempre en español rioplatense, con mensajes cortos y simples. "
            . "Netfan desarrolla software a medida, sitios web, sistemas y bots de WhatsApp para negocios. "
            . "No inventes teléfonos ni direcciones. Si no sabés algo, decí que no estás segura y ofrecé ayuda general. "
            . "Mantené un tono profesional pero cercano, como soporte técnico/consultor digital.";

        $stmtIns = $pdo->prepare("
            INSERT INTO bot 
            (id, nombre, activo, openai_api_key, modelo, prompt,
             max_dias_memoria, max_mensajes_memoria, temperature, max_tokens)
            VALUES
            (1, 'Andrea', 0, NULL, 'gpt-3.5-turbo', :prompt, 7, 30, 0.60, 250)
        ");
        $stmtIns->execute([':prompt' => $defaultPrompt]);

        $stmt = $pdo->prepare("SELECT * FROM bot WHERE id = 1 LIMIT 1");
        $stmt->execute();
        $bot = $stmt->fetch(PDO::FETCH_ASSOC);
    }

    return $bot;
}

/* =========================================================
 * Memoria: construir contexto de conversación
 * ========================================================= */

function andrea_build_messages_context(PDO $pdo, int $contactoId, array $botConfig, string $mensajeActual): array
{
    $messages = [];

    $prompt = trim($botConfig['prompt'] ?? '');
    if ($prompt === '') {
        $prompt = "Eres Andrea, asistente virtual de Netfan. Respondés en español, breve y claro.";
    }
    $messages[] = [
        'role'    => 'system',
        'content' => $prompt,
    ];

    $maxDias = (int)($botConfig['max_dias_memoria'] ?? 7);
    $maxMsgs = (int)($botConfig['max_mensajes_memoria'] ?? 30);
    if ($maxMsgs < 1) $maxMsgs = 30;

    $desde = null;
    if ($maxDias > 0) {
        $dt = new DateTime();
        $dt->modify('-' . $maxDias . ' days');
        $desde = $dt->format('Y-m-d H:i:s');
    }

    $sql = "SELECT direccion, mensaje, fecha
            FROM charlas
            WHERE contacto_id = :cid
              AND tipo = 'texto'";
    $params = [':cid' => $contactoId];

    if ($desde !== null) {
        $sql .= " AND fecha >= :desde";
        $params[':desde'] = $desde;
    }

    $sql .= " ORDER BY fecha ASC LIMIT :limite";

    $stmt = $pdo->prepare($sql);
    foreach ($params as $k => $v) {
        if ($k === ':limite') continue;
        $stmt->bindValue($k, $v);
    }
    $stmt->bindValue(':limite', $maxMsgs, PDO::PARAM_INT);
    $stmt->execute();
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

    foreach ($rows as $row) {
        $msg = trim((string)$row['mensaje']);
        if ($msg === '') continue;

        $role = ($row['direccion'] === 'enviado') ? 'assistant' : 'user';
        $messages[] = [
            'role'    => $role,
            'content' => $msg,
        ];
    }

    $last = end($messages);
    if (!($last && $last['role'] === 'user' && trim($last['content']) === trim($mensajeActual))) {
        $messages[] = [
            'role'    => 'user',
            'content' => $mensajeActual,
        ];
    }

    return $messages;
}

/* =========================================================
 * OpenAI
 * ========================================================= */

function andrea_call_openai(array $messages, array $botConfig): ?string
{
    $apiKey = trim($botConfig['openai_api_key'] ?? '');
    if ($apiKey === '') {
        andrea_log("API key de OpenAI vacía. No se llama al modelo.");
        return null;
    }

    $modelo = trim($botConfig['modelo'] ?? 'gpt-3.5-turbo');
    if ($modelo === '') {
        $modelo = 'gpt-3.5-turbo';
    }

    $temperature = (float)($botConfig['temperature'] ?? 0.6);
    if ($temperature < 0) $temperature = 0;
    if ($temperature > 1) $temperature = 1;

    $maxTokens = (int)($botConfig['max_tokens'] ?? 250);
    if ($maxTokens < 1) $maxTokens = 250;

    $payload = [
        'model'       => $modelo,
        'messages'    => $messages,
        'temperature' => $temperature,
        'max_tokens'  => $maxTokens,
    ];

    $url = 'https://api.openai.com/v1/chat/completions';

    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $apiKey,
        ],
        CURLOPT_POSTFIELDS     => json_encode($payload),
        CURLOPT_TIMEOUT        => 25,
    ]);

    $response = curl_exec($ch);
    $err      = curl_error($ch);
    $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($err !== '' || $status < 200 || $status >= 300) {
        andrea_log("ERROR GPT status={$status} err={$err} resp={$response}");
        return null;
    }

    $data = json_decode($response, true);
    if (!isset($data['choices'][0]['message']['content'])) {
        andrea_log("GPT sin content: {$response}");
        return null;
    }

    return trim($data['choices'][0]['message']['content']);
}

/* =========================================================
 * Lógica de catálogo: productos/servicios/propiedades
 * ========================================================= */

function andrea_try_catalog_answer(PDO $pdo, int $contactoId, string $telefono, string $bodyRaw, string $fecha): bool
{
    $text = andrea_normalize_text($bodyRaw);

    $stmt = $pdo->query("SELECT COUNT(*) FROM productos WHERE activo = 1");
    $total = (int)$stmt->fetchColumn();
    if ($total === 0) {
        return false;
    }

    $keywords = [
        'casa', 'casas', 'departamento', 'departamentos',
        'propiedad', 'propiedades', 'inmueble', 'inmuebles',
        'producto', 'productos', 'servicio', 'servicios',
        'disponible', 'disponibles', 'oferta', 'ofertas',
        'mostrame', 'muestrame', 'muéstrame',
        'lista', 'listado',
        'que tenes', 'qué tenes', 'que tienes', 'qué tienes',
        'que hay', 'qué hay'
    ];

    $hayKeyword = false;
    foreach ($keywords as $kw) {
        if (mb_strpos($text, $kw) !== false) {
            $hayKeyword = true;
            break;
        }
    }
    if (!$hayKeyword) {
        if (!(mb_strpos($text, 'disponible') !== false || mb_strpos($text, 'disponibles') !== false)) {
            return false;
        }
    }

    // Buscar barrio según nombres existentes en DB
    $filtroBarrio = null;
    $stmtB = $pdo->query("SELECT DISTINCT barrio FROM productos WHERE activo = 1 AND barrio IS NOT NULL AND barrio <> ''");
    $barrios = $stmtB->fetchAll(PDO::FETCH_COLUMN);

    foreach ($barrios as $barrio) {
        $bLower = mb_strtolower($barrio, 'UTF-8');
        if ($bLower !== '' && mb_strpos($text, $bLower) !== false) {
            $filtroBarrio = $barrio;
            break;
        }
    }

    // Categoría simple
    $filtroCategoria = null;
    if (mb_strpos($text, 'casa') !== false) {
        $filtroCategoria = 'casa';
    } elseif (mb_strpos($text, 'departamento') !== false || mb_strpos($text, 'depto') !== false) {
        $filtroCategoria = 'depto';
    }

    // Rango de precio básico
    $precioMin = null;
    $precioMax = null;

    if (preg_match_all('/\d[\d\.]*/', $text, $m)) {
        $nums = $m[0];
        $valores = [];
        foreach ($nums as $n) {
            $n = str_replace('.', '', $n);
            $val = (float)$n;
            if ($val > 0) {
                $valores[] = $val;
            }
        }
        if (count($valores) >= 2) {
            sort($valores);
            $precioMin = $valores[0];
            $precioMax = $valores[1];
        } elseif (count($valores) === 1) {
            $v = $valores[0];
            if (mb_strpos($text, 'hasta') !== false || mb_strpos($text, 'menos de') !== false) {
                $precioMax = $v;
            } elseif (mb_strpos($text, 'desde') !== false || mb_strpos($text, 'más de') !== false || mb_strpos($text, 'mas de') !== false) {
                $precioMin = $v;
            }
        }
    }

    $sql = "SELECT * FROM productos WHERE activo = 1";
    $params = [];

    if ($filtroBarrio !== null) {
        $sql .= " AND barrio = :barrio";
        $params[':barrio'] = $filtroBarrio;
    }
    if ($filtroCategoria !== null) {
        $sql .= " AND categoria LIKE :cat";
        $params[':cat'] = '%' . $filtroCategoria . '%';
    }
    if ($precioMin !== null) {
        $sql .= " AND precio >= :pmin";
        $params[':pmin'] = $precioMin;
    }
    if ($precioMax !== null) {
        $sql .= " AND precio <= :pmax";
        $params[':pmax'] = $precioMax;
    }

    $sql .= " ORDER BY destacado DESC, precio ASC, created_at DESC LIMIT 20";

    $stmt = $pdo->prepare($sql);
    foreach ($params as $k => $v) {
        if ($k === ':pmin' || $k === ':pmax') {
            $stmt->bindValue($k, $v, PDO::PARAM_STR);
        } else {
            $stmt->bindValue($k, $v);
        }
    }
    $stmt->execute();
    $items = $stmt->fetchAll(PDO::FETCH_ASSOC);

    if (empty($items)) {
        $resp = "Por ahora no encontré nada con esos filtros.";

        if ($filtroBarrio !== null) {
            $resp .= " Barrio: " . $filtroBarrio . ".";
        }
        if ($precioMin !== null || $precioMax !== null) {
            $resp .= " Rango de precio pedido.";
        }
        $resp .= " Podés preguntarme con menos filtros o pedirme el listado general.";

        if (andrea_enviar_whatsapp($telefono, $resp)) {
            $stmtIns = $pdo->prepare("
                INSERT INTO charlas (contacto_id, direccion, tipo, mensaje, media_path, fecha)
                VALUES (:cid, 'enviado', 'texto', :msg, NULL, :fecha)
            ");
            $stmtIns->execute([
                ':cid'   => $contactoId,
                ':msg'   => $resp,
                ':fecha' => $fecha,
            ]);
        }

        andrea_log("Catálogo: sin resultados para filtros -> respuesta enviada.");
        return true;
    }

    $lineas = [];
    $intro = "Te paso lo que tengo disponible";

    $detalles = [];
    if ($filtroCategoria !== null) {
        $detalles[] = $filtroCategoria;
    }
    if ($filtroBarrio !== null) {
        $detalles[] = "barrio " . $filtroBarrio;
    }
    if ($precioMin !== null || $precioMax !== null) {
        if ($precioMin !== null && $precioMax !== null) {
            $detalles[] = "entre " . number_format($precioMin, 0, ',', '.') . " y " . number_format($precioMax, 0, ',', '.');
        } elseif ($precioMin !== null) {
            $detalles[] = "desde " . number_format($precioMin, 0, ',', '.');
        } elseif ($precioMax !== null) {
            $detalles[] = "hasta " . number_format($precioMax, 0, ',', '.');
        }
    }

    if (!empty($detalles)) {
        $intro .= " (" . implode(', ', $detalles) . ")";
    }
    $intro .= ":";

    $lineas[] = "Andrea: " . $intro;

    $i = 1;
    foreach ($items as $item) {
        $titulo      = $item['titulo'] ?? '';
        $categoria   = $item['categoria'] ?? '';
        $barrio      = $item['barrio'] ?? '';
        $ciudad      = $item['ciudad'] ?? '';
        $precio      = $item['precio'];
        $moneda      = $item['moneda'] ?? 'ARS';
        $descripcion = $item['descripcion'] ?? '';
        $foto_url    = $item['foto_url'] ?? '';
        $link_pago   = $item['link_pago'] ?? '';

        $precioTxt = '-';
        if ($precio !== null) {
            $precioTxt = $moneda . ' ' . number_format((float)$precio, 0, ',', '.');
        }

        $partes = [];
        $partes[] = "{$i}) {$titulo}";
        if ($categoria !== '') {
            $partes[] = "Tipo: {$categoria}";
        }
        if ($barrio !== '' || $ciudad !== '') {
            $loc = trim($barrio . ' ' . $ciudad);
            if ($loc !== '') {
                $partes[] = "Zona: {$loc}";
            }
        }
        if ($precioTxt !== '-') {
            $partes[] = "Precio: {$precioTxt}";
        }
        if ($descripcion !== '') {
            $partes[] = "Detalle: " . mb_substr($descripcion, 0, 160) . (mb_strlen($descripcion) > 160 ? "..." : "");
        }
        if ($foto_url !== '') {
            $partes[] = "Foto: {$foto_url}";
        }
        if ($link_pago !== '') {
            $partes[] = "Pago: {$link_pago}";
        }

        $lineas[] = implode(" | ", $partes);
        $i++;
    }

    $respuesta = implode("\n\n", $lineas);

    if (andrea_enviar_whatsapp($telefono, $respuesta)) {
        $stmtIns = $pdo->prepare("
            INSERT INTO charlas (contacto_id, direccion, tipo, mensaje, media_path, fecha)
            VALUES (:cid, 'enviado', 'texto', :msg, NULL, :fecha)
        ");
        $stmtIns->execute([
            ':cid'   => $contactoId,
            ':msg'   => $respuesta,
            ':fecha' => $fecha,
        ]);
        andrea_log("Catálogo: respuesta enviada con " . count($items) . " items.");
    }

    return true;
}

/* =========================================================
 * Lógica de Andrea
 * ========================================================= */

function andrea_handle_incoming(PDO $pdo, int $contactoId, string $telefono, string $bodyRaw, string $fecha): void
{
    $botConfig = andrea_get_bot_config($pdo);

    if ((int)$botConfig['activo'] !== 1) {
        andrea_log("Bot inactivo. No se responde.");
        return;
    }

    $farewellType = andrea_classify_farewell($bodyRaw);

    if ($farewellType === 'silent') {
        andrea_log("Mensaje clasificado como farewell SILENT. No se responde.");
        return;
    }

    if ($farewellType === 'bye') {
        $bye = "Andrea: Gracias por escribir. ¡Hasta luego!";
        if (andrea_enviar_whatsapp($telefono, $bye)) {
            $stmt = $pdo->prepare("
                INSERT INTO charlas (contacto_id, direccion, tipo, mensaje, media_path, fecha)
                VALUES (:cid, 'enviado', 'texto', :msg, NULL, :fecha)
            ");
            $stmt->execute([
                ':cid'   => $contactoId,
                ':msg'   => $bye,
                ':fecha' => $fecha,
            ]);
        }
        andrea_log("Respuesta de despedida enviada.");
        return;
    }

    if (andrea_try_catalog_answer($pdo, $contactoId, $telefono, $bodyRaw, $fecha)) {
        return;
    }

    $messages = andrea_build_messages_context($pdo, $contactoId, $botConfig, $bodyRaw);
    $reply    = andrea_call_openai($messages, $botConfig);

    if ($reply === null || $reply === '') {
        $reply = "Andrea: Soy la asistente virtual de Netfan. Desarrollamos software a medida, sitios web y bots de WhatsApp para negocios. Contame qué necesitás y veo cómo ayudarte.";
    }

    $final = $reply;

    if (andrea_enviar_whatsapp($telefono, $final)) {
        $stmt = $pdo->prepare("
            INSERT INTO charlas (contacto_id, direccion, tipo, mensaje, media_path, fecha)
            VALUES (:cid, 'enviado', 'texto', :msg, NULL, :fecha)
        ");
        $stmt->execute([
            ':cid'   => $contactoId,
            ':msg'   => $final,
            ':fecha' => $fecha,
        ]);
        andrea_log("Respuesta GPT enviada y registrada en charlas.");
    } else {
        andrea_log("ERROR al enviar respuesta por WhatsApp.");
    }
}

/* =========================================================
 * Lógica principal del webhook UltraMsg
 * ========================================================= */

try {
    $raw = file_get_contents('php://input');

    file_put_contents(
        __DIR__ . '/debug_ultramsg.log',
        "==== " . date('Y-m-d H:i:s') . " ====\n" . $raw . "\n\n",
        FILE_APPEND
    );

    $payload = json_decode($raw, true);

    if (!is_array($payload) || empty($payload['data'])) {
        http_response_code(200);
        echo "Sin data válida";
        exit;
    }

    $eventType = $payload['event_type'] ?? '';
    
    file_put_contents(
        __DIR__ . '/debug_ultramsg.log',
        "EVENTO DETECTADO: {$eventType}\n",
        FILE_APPEND
    );

    if ($eventType && !in_array($eventType, ['message_received', 'message_create'])) {
        http_response_code(200);
        echo "Evento ignorado: $eventType";
        exit;
    }

    $data = $payload['data'];

    $fromMe = !empty($data['fromMe']);
    
    if ($fromMe) {
        $telefono  = $data['to'] ?? '';
        $direccion = 'enviado';
    } else {
        $telefono  = $data['from'] ?? '';
        $direccion = 'recibido';
    }

    if (!$telefono) {
        http_response_code(200);
        echo "Sin telefono";
        exit;
    }

    if (strpos($telefono, '@') !== false) {
        $telefono = substr($telefono, 0, strpos($telefono, '@'));
    }
    $telefono = trim($telefono);

    file_put_contents(
        __DIR__ . '/debug_ultramsg.log',
        "DIRECCIÓN: {$direccion} | TELÉFONO: {$telefono} | fromMe: " . ($fromMe ? 'SI' : 'NO') . "\n",
        FILE_APPEND
    );

    $nombre   = isset($data['pushname']) && $data['pushname'] !== '' ? $data['pushname'] : null;
    $bodyRaw  = isset($data['body']) ? (string)$data['body'] : '';
    $body     = $bodyRaw;
    $time     = isset($data['time']) ? (int)$data['time'] : time();
    $fecha    = date('Y-m-d H:i:s', $time);

    $tipoUltra = $data['type'] ?? 'chat';

    $tipoMensaje = 'texto';
    switch ($tipoUltra) {
        case 'image':
        case 'sticker':
            $tipoMensaje = 'imagen';
            break;
        case 'audio':
        case 'ptt':
            $tipoMensaje = 'audio';
            break;
        case 'video':
        case 'document':
            $tipoMensaje = 'documento';
            break;
        case 'chat':
        default:
            $tipoMensaje = 'texto';
            break;
    }

    if ($body === '') {
        $body = '[Sin texto]';
    }

    $mediaUrl    = isset($data['media']) ? trim($data['media']) : '';
    $mediaPathDb = null;

    if ($mediaUrl !== '') {
        if (!is_dir($BASE_MEDIA_DIR)) {
            @mkdir($BASE_MEDIA_DIR, 0775, true);
        }

        $subDir  = date('Y') . '/' . date('m') . '/' . date('d');
        $destDir = $BASE_MEDIA_DIR . '/' . $subDir;
        if (!is_dir($destDir)) {
            @mkdir($destDir, 0775, true);
        }

        $urlPath       = parse_url($mediaUrl, PHP_URL_PATH);
        $nombreArchivo = $urlPath ? basename($urlPath) : (uniqid('media_', true) . '.bin');
        $destPath      = $destDir . '/' . $nombreArchivo;

        $fileData = false;

        if (filter_var($mediaUrl, FILTER_VALIDATE_URL) && ini_get('allow_url_fopen')) {
            $fileData = @file_get_contents($mediaUrl);
        }

        if ($fileData === false && function_exists('curl_init') && filter_var($mediaUrl, FILTER_VALIDATE_URL)) {
            $ch = curl_init($mediaUrl);
            curl_setopt_array($ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_TIMEOUT        => 30,
            ]);
            $fileData = curl_exec($ch);
            $curlErr  = curl_error($ch);
            curl_close($ch);

            if ($fileData === false || $curlErr) {
                file_put_contents(
                    __DIR__ . '/debug_ultramsg.log',
                    "ERROR descargando media con cURL: {$mediaUrl} - {$curlErr}\n",
                    FILE_APPEND
                );
            }
        }

        if ($fileData !== false) {
            if (file_put_contents($destPath, $fileData) !== false) {
                $mediaPathDb = 'media/' . $subDir . '/' . $nombreArchivo;
            } else {
                file_put_contents(
                    __DIR__ . '/debug_ultramsg.log',
                    "ERROR guardando media en disco: {$destPath}\n",
                    FILE_APPEND
                );
            }
        } else {
            $mediaPathDb = $mediaUrl;
            file_put_contents(
                __DIR__ . '/debug_ultramsg.log',
                "NO SE PUDO DESCARGAR MEDIA, se guarda URL directa: {$mediaUrl}\n",
                FILE_APPEND
            );
        }
    }

    $pdo = db();
    $pdo->beginTransaction();

    $sql = "INSERT IGNORE INTO contactos (telefono, nombre, fecha_alta)
            VALUES (:tel, :nom, NOW())";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([
        ':tel' => $telefono,
        ':nom' => $nombre,
    ]);

    if ($nombre !== null) {
        $sql = "UPDATE contactos SET nombre = :nom
                WHERE telefono = :tel AND (nombre IS NULL OR nombre = '')";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([
            ':nom' => $nombre,
            ':tel' => $telefono,
        ]);
    }

    $sql = "SELECT id FROM contactos WHERE telefono = :tel LIMIT 1";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([':tel' => $telefono]);
    $contactoId = (int)$stmt->fetchColumn();

    if ($contactoId <= 0) {
        throw new Exception('No se pudo obtener ID del contacto para ' . $telefono);
    }

    $sql = "INSERT INTO charlas (contacto_id, direccion, tipo, mensaje, media_path, fecha)
            VALUES (:cid, :dir, :tipo, :msg, :media, :fecha)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([
        ':cid'   => $contactoId,
        ':dir'   => $direccion,
        ':tipo'  => $tipoMensaje,
        ':msg'   => $body,
        ':media' => $mediaPathDb,
        ':fecha' => $fecha,
    ]);

    $charlaId = $pdo->lastInsertId();

    $pdo->commit();

    file_put_contents(
        __DIR__ . '/debug_ultramsg.log',
        "✓ GUARDADO ID: {$charlaId} | Contacto: {$telefono} | Dir: {$direccion} | Tipo: {$tipoMensaje}\n\n",
        FILE_APPEND
    );

    try {
        if (
            !$fromMe &&
            $direccion === 'recibido' &&
            $tipoMensaje === 'texto' &&
            trim($bodyRaw) !== ''
        ) {
            andrea_handle_incoming($pdo, $contactoId, $telefono, $bodyRaw, $fecha);
        } else {
            andrea_log("Mensaje no aplicable a Andrea (fromMe=" . ($fromMe ? 'SI' : 'NO') . ", dir={$direccion}, tipo={$tipoMensaje})");
        }
    } catch (Throwable $e2) {
        andrea_log("ERROR interno Andrea: " . $e2->getMessage());
    }

    http_response_code(200);
    echo "OK contacto {$telefono}, mensaje guardado ({$direccion})";

} catch (Throwable $e) {
    if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
        $pdo->rollBack();
    }

    file_put_contents(
        __DIR__ . '/debug_ultramsg.log',
        "✗ ERROR: " . $e->getMessage() . "\n\n",
        FILE_APPEND
    );

    http_response_code(500);
    echo "ERROR: " . $e->getMessage();
}
