#!/usr/bin/env php
<?php
/**
 * Reindexador de embeddings (búsqueda semántica)
 *
 * Regenera los vectores de embeddings de artículos de la base de
 * conocimiento y de tickets. Úsalo después de cambiar el proveedor o el
 * modelo de embeddings (por ejemplo, al migrar de OpenAI a Ollama
 * nomic-embed-text): la dimensión del vector cambia y los embeddings
 * antiguos quedan inservibles hasta regenerarlos.
 *
 * Uso:
 *   php bin/reindex-embeddings                 - Reindexa artículos y tickets
 *   php bin/reindex-embeddings --type=articles - Solo artículos de KB
 *   php bin/reindex-embeddings --type=tickets  - Solo tickets
 *   php bin/reindex-embeddings --limit=200     - Limita la cantidad por tipo
 *   php bin/reindex-embeddings --actor-user-id=1
 *   php bin/reindex-embeddings --tenant=academico
 *   php bin/reindex-embeddings --dry-run       - Cuenta sin generar vectores
 *   php bin/reindex-embeddings --help
 */

$basePath = dirname(__DIR__);
define('BASE_PATH', $basePath . '/');

require $basePath . '/core/helpers/Env.php';
Env::load($basePath . '/.env');

$args = array_slice($argv, 1);

if (in_array('--help', $args, true) || in_array('-h', $args, true)) {
    echo "\nKlee — Reindexador de embeddings\n";
    echo "Uso:\n";
    echo "  php bin/reindex-embeddings\n";
    echo "  php bin/reindex-embeddings --type=articles|tickets|all\n";
    echo "  php bin/reindex-embeddings --limit=200\n";
    echo "  php bin/reindex-embeddings --actor-user-id=1\n";
    echo "  php bin/reindex-embeddings --tenant=academico\n";
    echo "  php bin/reindex-embeddings --dry-run\n\n";
    exit(0);
}

$actorUserId = null;
$tenantKey = '';
$type = 'all';
$limit = 0;
$dryRun = in_array('--dry-run', $args, true);

foreach ($args as $arg) {
    if (strpos($arg, '--actor-user-id=') === 0) {
        $parsed = (int)substr($arg, 16);
        if ($parsed > 0) {
            $actorUserId = $parsed;
        }
    } elseif (strpos($arg, '--tenant=') === 0) {
        $tenantKey = trim((string)substr($arg, 9));
    } elseif (strpos($arg, '--type=') === 0) {
        $type = strtolower(trim((string)substr($arg, 7)));
    } elseif (strpos($arg, '--limit=') === 0) {
        $limit = max(0, (int)substr($arg, 8));
    }
}

if (!in_array($type, array('all', 'articles', 'tickets'), true)) {
    echo "[error] --type debe ser articles, tickets o all.\n";
    exit(1);
}

if ($tenantKey !== '') {
    putenv('KLEE_TENANT=' . $tenantKey);
    $_ENV['KLEE_TENANT'] = $tenantKey;
    $_SERVER['KLEE_TENANT'] = $tenantKey;
}

if (file_exists($basePath . '/vendor/autoload.php')) {
    require $basePath . '/vendor/autoload.php';
}

$fallbackAutoloadSearchPaths = array(
    'core/',
    'core/traits/',
    'core/db/',
    'core/middleware/',
    'core/attributes/',
    'core/contracts/',
    'app/models/',
    'app/controllers/',
    'core/helpers/',
    'app/config/',
);

$fallbackClassFileMap = array(
    'DB' => 'core/Db.php',
    'Database' => 'core/Db.php',
    'FORM_VALIDATE' => 'core/Formvalidate.php',
    'FormValidator' => 'core/Formvalidate.php',
);

spl_autoload_register(function ($class) use ($basePath, $fallbackAutoloadSearchPaths, $fallbackClassFileMap) {
    if (isset($fallbackClassFileMap[$class])) {
        $mappedPath = $basePath . '/' . $fallbackClassFileMap[$class];
        if (file_exists($mappedPath)) {
            require_once $mappedPath;
            return;
        }
    }

    foreach ($fallbackAutoloadSearchPaths as $relativePath) {
        $classPath = $basePath . '/' . $relativePath . $class . '.php';
        if (file_exists($classPath)) {
            require_once $classPath;
            return;
        }
    }
}, true, true);

if (file_exists($basePath . '/app/config/Config.php')) {
    require $basePath . '/app/config/Config.php';
}
if (file_exists($basePath . '/app/config/ConfigEnv.php')) {
    require $basePath . '/app/config/ConfigEnv.php';
}

if (class_exists('TenantContext')) {
    TenantContext::bootstrap();
}

if (class_exists('Controller')) {
    Controller::syncConfigFromEnv();
}

if (class_exists('Config')) {
    @setlocale(LC_ALL, Config::getLocale());
    @date_default_timezone_set(Config::getTimezone());
}

if (session_status() === PHP_SESSION_NONE) {
    @session_start();
}

if (!class_exists('TicketsModel') || !class_exists('KnowledgeBaseModel')) {
    echo "[error] Modelos no disponibles. Verifica el autoload de Composer.\n";
    exit(2);
}

// Muestra el proveedor/modelo de embeddings activo para confirmar la migración.
if (class_exists('Config') && method_exists('Config', 'getAiConfiguration')) {
    $aiConfig = Config::getAiConfiguration();
    if (is_array($aiConfig)) {
        $providerLabel = !empty($aiConfig['isLocal']) ? 'local' : 'remoto';
        echo "Proveedor IA: " . (string)($aiConfig['provider'] ?? 'OpenAI')
            . " (" . $providerLabel . ") @ " . (string)($aiConfig['baseUrl'] ?? '') . "\n";
    }
}

$collectIds = function ($modelClass, $limit) {
    $criteria = array(
        'WHERE' => array(
            array('name' => 'Estado', 'value' => 1),
        ),
        'ORDER_BY' => array(
            'COLUMN' => array('Id'),
            'ORDEN' => array('DESC'),
        ),
    );

    if ($limit > 0) {
        $criteria['LIMIT'] = array('START' => 0, 'END' => (int)$limit);
    }

    $rows = $modelClass::getAll(array('Id'), $criteria, array());
    $ids = array();
    foreach ((array)$rows as $row) {
        $id = isset($row['Id']) ? (int)$row['Id'] : 0;
        if ($id > 0) {
            $ids[] = $id;
        }
    }

    return $ids;
};

$reindex = function ($label, $ids, callable $sync) use ($dryRun) {
    $total = count($ids);
    echo "\n[$label] " . $total . " registro(s) a procesar.\n";

    if ($dryRun) {
        echo "[$label] dry-run: no se generan vectores.\n";
        return array('ok' => 0, 'fail' => 0, 'total' => $total);
    }

    $ok = 0;
    $fail = 0;
    $i = 0;
    foreach ($ids as $id) {
        $i++;
        $result = false;
        try {
            $result = $sync($id);
        } catch (Throwable $exception) {
            $result = false;
        }

        if ($result) {
            $ok++;
        } else {
            $fail++;
        }

        if ($i % 25 === 0 || $i === $total) {
            echo "[$label] " . $i . "/" . $total . " (ok: " . $ok . ", fallidos: " . $fail . ")\n";
        }
    }

    return array('ok' => $ok, 'fail' => $fail, 'total' => $total);
};

$start = microtime(true);
$summary = array();

try {
    if ($type === 'all' || $type === 'articles') {
        $ids = $collectIds('KnowledgeBaseModel', $limit);
        $summary['articles'] = $reindex('articulos', $ids, function ($id) use ($actorUserId) {
            return KnowledgeBaseModel::syncArticleEmbedding($id, $actorUserId, true);
        });
    }

    if ($type === 'all' || $type === 'tickets') {
        $ids = $collectIds('TicketsModel', $limit);
        $summary['tickets'] = $reindex('tickets', $ids, function ($id) use ($actorUserId) {
            return TicketsModel::syncTicketEmbedding($id, $actorUserId, true);
        });
    }
} catch (Throwable $exception) {
    echo "[error] " . $exception->getMessage() . "\n";
    exit(1);
}

$elapsed = round(microtime(true) - $start, 2);

echo "\n──────────── Resumen ────────────\n";
foreach ($summary as $label => $data) {
    echo sprintf(
        "  %-10s total: %d  ok: %d  fallidos: %d\n",
        $label,
        (int)$data['total'],
        (int)$data['ok'],
        (int)$data['fail']
    );
}
echo "  Tiempo: {$elapsed}s\n\n";

exit(0);
