<?php
/*  ───────────────────────────────────────────────────────────────────
    generar_caracteristicas.php
    • Lee productos sin características grabadas (limit 10 para pruebas).
    • Pide a OpenAI un JSON → { product_type, specs { … } }.
    • Fusiona specs de OpenAI con:
       – Pre‐parsing de txtMedidas para ancho_cm, alto_cm, largo_cm.
       – Extracto de material desde OpenAI o txtMaterial.
       – Regex para capacidad_ml / capacidad_oz siempre.
       – Regex genérico para medidas “extra” solo si sobran cm.
    • Crea tipos de producto en tb_tipo_producto si no existen.
    • Crea materiales en tb_productos_materiales si no existen.
    • Inserta valores en tb_caracteristicas_productos (cuando sea material,
      inserta el ID de tb_productos_materiales como valor).
    • Actualiza idTipoProducto + columnas anchoProductoEnvio, altoProductoEnvio, largoProductoEnvio.
    ─────────────────────────────────────────────────────────────────── */

/* 1) CONFIG DB + OPENAI ------------------------------------------------ */
define('DB_SERVER',   'localhost');
define('DB_USERNAME', 'ndconsulta');
define('DB_PASSWORD', 'Nitro2021');
define('DB_NAME',     'db_compranet');
define('OPENAI_API_KEY', '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'); // ← Reemplaza con tu clave válida

/* 2) FUNCIÓN OpenAI ---------------------------------------------------- */
function callOpenAI(array $messages, array $functions, string $fn)
{
    $payload = [
        'model'         => 'gpt-3.5-turbo',
        'messages'      => $messages,
        'functions'     => $functions,
        'function_call' => ['name' => $fn],
        'max_tokens'    => 350,
        'temperature'   => 0.2
    ];
    $ch = curl_init('https://api.openai.com/v1/chat/completions');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . OPENAI_API_KEY,
            'Content-Type: application/json'
        ],
        CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE)
    ]);
    $raw  = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $j = json_decode($raw, true);
    return ($code === 200
        && isset($j['choices'][0]['message']['function_call']))
        ? $j['choices'][0]['message']['function_call']['arguments']
        : null;
}

/* 3) CONEXIÓN DB ------------------------------------------------------- */
$conn = new mysqli(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn->connect_error) die($conn->connect_error);

/* 4) SELECT productos sin características (limit 10 para pruebas) ------- */
$res = $conn->query("
    SELECT p.id, p.nombre, p.descripcion,
           p.txtMedidas, p.txtMaterial, p.txtMarca, p.txtAreaLogo
    FROM   tb_productos p
    LEFT   JOIN tb_caracteristicas_productos cp
           ON p.id = cp.idProducto
    WHERE  cp.valor IS NULL
    ORDER  BY p.id
");
if (!$res) die($conn->error);

/* 5) SCHEMA function-calling (specs abiertas) ------------------------- */
$fnSchema = [[
    'name'        => 'generate_specs',
    'description' => 'Devuelve product_type y todas las specs encontradas',
    'parameters'  => [
        'type'       => 'object',
        'properties' => [
            'product_type' => ['type' => 'string'],
            'specs'        => [
                'type'                 => 'object',
                'description'          => 'Características en snake_case',
                'additionalProperties' => [
                    'type' => ['string', 'number', 'boolean', 'null']
                ]
            ]
        ],
        'required' => ['product_type', 'specs']
    ]
]];

/* 6) HELPERS ----------------------------------------------------------- */
function normalizeWord(string $w): string
{
    $w = strtolower(trim($w));
    $w = iconv('UTF-8', 'ASCII//TRANSLIT', $w);
    $w = preg_replace('/[^a-z0-9_]/', '_', $w);
    $w = preg_replace('/_+/', '_', $w);
    $w = trim($w, '_');
    if (strpos($w, '_') !== false) {
        $parts = explode('_', $w);
        $w = $parts[0];
    }
    return $w === '' ? 'general' : $w;
}

/**
 * Inserta o recupera ID de la fila en $table con nombre=$nombre.
 * Para 'tb_tipo_producto' y 'tb_productos_materiales'.
 */
function upsertCatalog(mysqli $c, string $table, string $nombre): int
{
    $sql = "INSERT IGNORE INTO {$table} (nombre) VALUES (?)";
    $st  = $c->prepare($sql);
    if (!$st) {
        error_log("Prepare failed (INSERT IGNORE {$table}): " . $c->error);
        return 0;
    }
    $st->bind_param('s', $nombre);
    $st->execute();
    $st->close();

    $st  = $c->prepare("SELECT id FROM {$table} WHERE nombre=? LIMIT 1");
    if (!$st) {
        error_log("Prepare failed (SELECT id FROM {$table}): " . $c->error);
        return 0;
    }
    $st->bind_param('s', $nombre);
    $st->execute();
    $st->bind_result($id);
    $st->fetch();
    $st->close();
    return (int)$id;
}

/* 7) LOOP principal ---------------------------------------------------- */
while ($p = $res->fetch_assoc()) {
    $idProd = (int)$p['id'];
    echo "∙ Producto {$idProd}\n";

    /* 7.1 PROMPT alineado a specs abiertas */
    $messages = [[
        'role' => 'system',
        'content' => "
Devuelve SOLO un JSON con estas dos claves:
{
  \"product_type\": \"<una_palabra_snake_case>\",
  \"specs\": {
    …todas las características encontradas…
    (material, capacidad_ml, alto_cm, ancho_cm, largo_cm,
     diametro_cm, fuelle_cm, grosor_mm, etc.)
  }
}
Si no encuentras ninguna especificación, pon \"specs\": {}.
No incluyas texto fuera del JSON."
    ], [
        'role' => 'user',
        'content' => "
Nombre: {$p['nombre']}
Descripción: {$p['descripcion']}
Medidas declaradas: {$p['txtMedidas']}
Material declarado: {$p['txtMaterial']}
Tipo impresión: {$p['txtMarca']}
Área logo: {$p['txtAreaLogo']}"
    ]];

    /* 7.2 OpenAI → JSON */
    $args = callOpenAI($messages, $fnSchema, 'generate_specs');
    if (!$args) {
        echo "  ⚠️ Error OpenAI\n";
        continue;
    }

    /* Depuración inicial */
    file_put_contents(
        __DIR__ . '/debug_specs.log',
        "ID $idProd → " . $args . PHP_EOL,
        FILE_APPEND
    );

    $d = json_decode($args, true);
    if (!$d || !isset($d['product_type'])) {
        echo "  ⚠️ JSON inválido o sin product_type\n";
        continue;
    }

    /* 7.3 EXTRAER product_type y specs inicial de OpenAI */
    $tipo  = normalizeWord($d['product_type']);
    $specs = $d['specs'] ?? [];

    // — Limpiar “material” si OpenAI lo devolvió como string —
    if (isset($d['specs']['material'])) {
        $mat = strtolower(trim($d['specs']['material']));
        $mat = iconv('UTF-8', 'ASCII//TRANSLIT', $mat);
        $mat = preg_replace('/[^a-z0-9_]/', '_', $mat);
        $mat = preg_replace('/_+/', '_', $mat);
        $mat = trim($mat, '_');
        $specs['material'] = $mat;
    }

    /* 7.4 PRE‐PARSING de txtMedidas para ancho_cm, alto_cm, largo_cm */
    if (!empty($p['txtMedidas'])) {
        if (preg_match(
            '/([\d.,]+)\s*cm\s*[×x]\s*([\d.,]+)\s*cm\s*[×x]\s*([\d.,]+)\s*cm/i',
            $p['txtMedidas'],
            $m
        )) {
            $anchoVal = floatval(str_replace(',', '.', $m[1]));
            $altoVal   = floatval(str_replace(',', '.', $m[2]));
            $largoVal  = floatval(str_replace(',', '.', $m[3]));

            if (!isset($specs['ancho_cm'])) {
                $specs['ancho_cm'] = $anchoVal;
            }
            if (!isset($specs['alto_cm'])) {
                $specs['alto_cm'] = $altoVal;
            }
            if (!isset($specs['largo_cm'])) {
                $specs['largo_cm'] = $largoVal;
            }
        }
    }

    /* 7.5 Regex para capacidad_ml / capacidad_oz siempre */
    if (preg_match('/([\d.,]+)\s*ml\b/i', "{$p['descripcion']} {$p['txtMedidas']}", $m_ml)) {
        $specs['capacidad_ml'] = floatval(str_replace(',', '.', $m_ml[1]));
    }
    if (preg_match('/([\d.,]+)\s*oz\b/i', "{$p['descripcion']} {$p['txtMedidas']}", $m_oz)) {
        $specs['capacidad_oz'] = floatval(str_replace(',', '.', $m_oz[1]));
    }

    /* 7.6 Fallback material si no existe en specs */
    if (!isset($specs['material']) && !empty($p['txtMaterial'])) {
        $mat = strtolower(trim($p['txtMaterial']));
        $mat = iconv('UTF-8', 'ASCII//TRANSLIT', $mat);
        $mat = preg_replace('/[^a-z0-9_]/', '_', $mat);
        $mat = preg_replace('/_+/', '_', $mat);
        $mat = trim($mat, '_');
        $specs['material'] = $mat;
    }

    /* 7.7 Fallback genérico para “medida_X_cm” solo si faltan dimensiones */
    if (
        !isset($specs['ancho_cm']) &&
        !isset($specs['alto_cm'])  &&
        !isset($specs['largo_cm'])
    ) {
        $texto = "{$p['descripcion']} {$p['txtMedidas']}";
        if (preg_match_all('/(\d+(?:[.,]\d+)?)\s*(cm)\b/i', $texto, $m, PREG_SET_ORDER)) {
            foreach ($m as $i => $mx) {
                $val = str_replace(',', '.', $mx[1]);
                $key = "medida_{$i}_cm";
                if (!isset($specs[$key])) {
                    $specs[$key] = $val;
                }
            }
        }
    }

    /* 7.8 INSERTAR en tb_tipo_producto ------------------------------ */
    $tipoId = upsertCatalog($conn, 'tb_tipo_producto', $tipo);
    if ($tipoId === 0) {
        echo "  ⚠️ No se obtuvo ID para tipo_producto: '{$tipo}'\n";
        $tipoId = 1; // “general” por defecto
    } else {
        echo "  → tipo_producto '{$tipo}' = ID {$tipoId}\n";
    }
    $st = $conn->prepare("UPDATE tb_productos SET idTipoProducto=? WHERE id=?");
    if ($st) {
        $st->bind_param('ii', $tipoId, $idProd);
        if (!$st->execute()) {
            echo "  ⚠️ Error UPDATE idTipoProducto: " . $st->error . "\n";
        }
        $st->close();
    } else {
        echo "  ⚠️ Prepare UPDATE idTipoProducto fallido: " . $conn->error . "\n";
    }

    /* 7.9 INSERTAR CARACTERÍSTICAS en tb_lista_caracteristica +
            tb_caracteristicas_productos -------------------------- */
    foreach ($specs as $nombre => $valor) {
        if ($valor === null || $valor === '') continue;

        // Si es 'material', primero insertar en tb_productos_materiales
        if ($nombre === 'material') {
            $matId = upsertCatalog($conn, 'tb_productos_materiales', $valor);
            if ($matId === 0) {
                echo "    ⚠️ No se obtuvo ID para material: '{$valor}'\n";
                continue;
            }
            echo "    → material '{$valor}' = ID_MATERIAL {$matId}\n";

            // Guardar la característica 'material' con valor = ID del material
            $carId = upsertCatalog($conn, 'tb_lista_caracteristica', 'material');
            $sql   = "INSERT INTO tb_caracteristicas_productos
                      (idProducto, idCaracteristica, valor)
                      VALUES (?,?,?)
                      ON DUPLICATE KEY UPDATE valor = VALUES(valor)";
            $st    = $conn->prepare($sql);
            if ($st) {
                $valorStr = strval($matId); // guardamos el ID como texto
                $st->bind_param('iis', $idProd, $carId, $valorStr);
                if (!$st->execute()) {
                    echo "    ⚠️ Error INSERT material: " . $st->error . "\n";
                }
                $st->close();
            } else {
                echo "    ⚠️ Prepare INSERT material fallido: " . $conn->error . "\n";
            }
            continue;
        }

        // Para todas las demás características normales…
        $carId = upsertCatalog($conn, 'tb_lista_caracteristica', $nombre);
        echo "    → caracteristica '{$nombre}' = ID {$carId}, valor='{$valor}'\n";

        $sql = "INSERT INTO tb_caracteristicas_productos
                (idProducto, idCaracteristica, valor)
                VALUES (?,?,?)
                ON DUPLICATE KEY UPDATE valor = VALUES(valor)";
        $st  = $conn->prepare($sql);
        if ($st) {
            $valorStr = strval($valor);
            $st->bind_param('iis', $idProd, $carId, $valorStr);
            if (!$st->execute()) {
                echo "    ⚠️ Error INSERT características: " . $st->error . "\n";
            }
            $st->close();
        } else {
            echo "    ⚠️ Prepare INSERT características fallido: " . $conn->error . "\n";
        }
    }

    /* 7.10 ACTUALIZAR medidas directas en tb_productos -------------- */
    $anchoVal = $specs['ancho_cm']  ?? null;
    $altoVal  = $specs['alto_cm']   ?? null;
    $largoVal = $specs['largo_cm']  ?? null;

    if ($anchoVal !== null || $altoVal !== null || $largoVal !== null) {
        $st = $conn->prepare("
            UPDATE tb_productos
            SET anchoProductoEnvio = COALESCE(?, anchoProductoEnvio),
                altoProductoEnvio  = COALESCE(?, altoProductoEnvio),
                largoProductoEnvio = COALESCE(?, largoProductoEnvio)
            WHERE id = ?");
        if ($st) {
            $st->bind_param('dddi', $anchoVal, $altoVal, $largoVal, $idProd);
            if (!$st->execute()) {
                echo "  ⚠️ Error UPDATE medidas: " . $st->error . "\n";
            }
            $st->close();
        } else {
            echo "  ⚠️ Prepare UPDATE medidas fallido: " . $conn->error . "\n";
        }
    }

    echo "  ✅ Procesado\n\n";
}

$conn->close();
echo "Fin.\n";
