// Tests para lua_engine_cpp_core.
// Catch2 amalgamated. No requiere ImGui context.
#include <catch_amalgamated.hpp>
#include "core/lua_engine.h"

// Helper: compila + eval con RowCtx vacia (sin columnas).
static std::string quick_eval(const std::string& formula, std::string* err = nullptr) {
    lua_engine::Engine* e = lua_engine::get();
    std::string compile_err;
    int id = lua_engine::compile(e, formula, &compile_err);
    if (id < 0) {
        if (err) *err = compile_err;
        return "";
    }
    lua_engine::RowCtx ctx;
    std::string eval_err;
    std::string result = lua_engine::eval(e, id, ctx, &eval_err);
    lua_engine::release(e, id);
    if (err) *err = eval_err;
    return result;
}

TEST_CASE("lua_engine: eval expr simple", "[lua_engine]") {
    std::string err;
    std::string result = quick_eval("2 + 3", &err);
    REQUIRE(err.empty());
    REQUIRE(result == "5");
}

TEST_CASE("lua_engine: eval con vars via RowCtx", "[lua_engine]") {
    // Prepara tabla: 1 fila, 2 columnas (price=10, qty=3).
    const char* price_str  = "10";
    const char* qty_str    = "3";
    const char* cells[]    = { price_str, qty_str };

    std::vector<std::string> headers = { "price", "qty" };
    std::unordered_map<std::string,int> name_to_col = { {"price", 0}, {"qty", 1} };

    data_table::ColumnType types[] = {
        data_table::ColumnType::Float,
        data_table::ColumnType::Float
    };

    lua_engine::RowCtx ctx;
    ctx.cells        = cells;
    ctx.orig_cols    = 2;
    ctx.row          = 0;
    ctx.header_names = &headers;
    ctx.name_to_col  = &name_to_col;
    ctx.types_orig   = types;
    ctx.n_types_orig = 2;

    lua_engine::Engine* e = lua_engine::get();
    std::string err;
    int id = lua_engine::compile(e, "[price] * [qty]", &err);
    REQUIRE(err.empty());
    REQUIRE(id >= 0);

    std::string result = lua_engine::eval(e, id, ctx, &err);
    lua_engine::release(e, id);
    REQUIRE(err.empty());
    // 10 * 3 = 30 (integer)
    REQUIRE(result == "30");
}

TEST_CASE("lua_engine: error en expr invalida", "[lua_engine]") {
    std::string err;
    int id = lua_engine::compile(lua_engine::get(), "this is not lua !!!", &err);
    REQUIRE(id < 0);
    REQUIRE(!err.empty());
}

TEST_CASE("lua_engine: sandbox bloquea io.open", "[lua_engine]") {
    std::string err;
    // io fue eliminado del sandbox; acceder a io.open debe producir error en runtime.
    std::string result = quick_eval("io.open('/etc/passwd', 'r')", &err);
    // Debe fallar (io es nil -> intento de indexar nil -> runtime error).
    REQUIRE(!err.empty());
    REQUIRE(result.empty());
}

TEST_CASE("lua_engine: preprocess convierte [col] a row[\"col\"]", "[lua_engine]") {
    std::string out = lua_engine::preprocess("[price] * [qty]");
    // Debe contener row["price"] y row["qty"], y auto-return al ser expresion.
    REQUIRE(out.find("row[\"price\"]") != std::string::npos);
    REQUIRE(out.find("row[\"qty\"]")   != std::string::npos);
    REQUIRE(out.rfind("return", 0) == 0);
}

TEST_CASE("lua_engine: fn.* builtins disponibles", "[lua_engine]") {
    std::string err;
    std::string result = quick_eval("fn.upper('hello')", &err);
    REQUIRE(err.empty());
    REQUIRE(result == "HELLO");
}

TEST_CASE("lua_engine: shutdown y reinicio", "[lua_engine]") {
    lua_engine::shutdown();
    // Tras shutdown, get() debe crear un nuevo estado limpio.
    lua_engine::Engine* e = lua_engine::get();
    REQUIRE(e != nullptr);
    std::string err;
    std::string result = quick_eval("1 + 1", &err);
    REQUIRE(err.empty());
    REQUIRE(result == "2");
    lua_engine::shutdown();
}
