// test_tql_to_sql.cpp — Catch2 tests for tql_to_sql (pure SQL emitter).
// Issue 0081. No DuckDB linked — only validates emitted SQL strings.
#include "catch_amalgamated.hpp"
#include "core/tql_to_sql.h"
#include "core/tql_helpers.h"

#include <string>
#include <vector>

using namespace data_table;
using namespace tql_to_sql;

// Helper: build a minimal State with one Stage.
static State make_state_one_stage() {
    State st;
    st.stages.push_back(Stage{});
    st.active_stage = 0;
    return st;
}

// Helper: build a TableInput for tests.
static TableInput make_table(const std::string& name,
                              const std::vector<std::string>& headers) {
    TableInput ti;
    ti.name    = name;
    ti.headers = headers;
    return ti;
}

// ============================================================================
// transpile_expr tests
// ============================================================================

TEST_CASE("transpile_expr: simple arithmetic", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h = {"price", "qty"};
    std::string out = transpile_expr("[price] * [qty]", h, err);
    REQUIRE(err.empty());
    REQUIRE(out.find("\"price\"") != std::string::npos);
    REQUIRE(out.find("\"qty\"")   != std::string::npos);
    REQUIRE(out.find(" * ")       != std::string::npos);
}

TEST_CASE("transpile_expr: numeric literal", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h = {"x"};
    std::string out = transpile_expr("42", h, err);
    REQUIRE(err.empty());
    REQUIRE(out == "42");
}

TEST_CASE("transpile_expr: string literal", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h = {};
    std::string out = transpile_expr("\"hello\"", h, err);
    REQUIRE(err.empty());
    REQUIRE(out == "'hello'");
}

TEST_CASE("transpile_expr: boolean literals", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h;
    REQUIRE(transpile_expr("true", h, err) == "TRUE");
    REQUIRE(err.empty());
    REQUIRE(transpile_expr("false", h, err) == "FALSE");
    REQUIRE(err.empty());
    REQUIRE(transpile_expr("nil", h, err) == "NULL");
    REQUIRE(err.empty());
}

TEST_CASE("transpile_expr: ternary if/then/else", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h = {"score"};
    std::string out = transpile_expr("if [score] > 90 then \"A\" else \"B\" end", h, err);
    REQUIRE(err.empty());
    REQUIRE(out.find("CASE WHEN") != std::string::npos);
    REQUIRE(out.find("THEN")      != std::string::npos);
    REQUIRE(out.find("ELSE")      != std::string::npos);
    REQUIRE(out.find("END")       != std::string::npos);
}

TEST_CASE("transpile_expr: string concat", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h = {"first", "last"};
    std::string out = transpile_expr("[first] .. \" \" .. [last]", h, err);
    REQUIRE(err.empty());
    REQUIRE(out.find(" || ") != std::string::npos);
}

TEST_CASE("transpile_expr: whitelisted math function", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h = {"x"};
    std::string out = transpile_expr("math.abs([x])", h, err);
    REQUIRE(err.empty());
    REQUIRE(out.find("abs(") != std::string::npos);
}

TEST_CASE("transpile_expr: forbidden keyword returns error", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h;
    transpile_expr("function() end", h, err);
    REQUIRE_FALSE(err.empty());
}

TEST_CASE("transpile_expr: bare identifier not allowed", "[tql_to_sql]") {
    std::string err;
    std::vector<std::string> h;
    transpile_expr("foo", h, err);
    REQUIRE_FALSE(err.empty());
}

// ============================================================================
// is_transpilable tests
// ============================================================================

TEST_CASE("is_transpilable: simple expression is transpilable", "[tql_to_sql]") {
    std::string err;
    bool ok = is_transpilable("[price] * 1.1", err);
    REQUIRE(ok);
    REQUIRE(err.empty());
}

TEST_CASE("is_transpilable: loop keyword not transpilable", "[tql_to_sql]") {
    std::string err;
    bool ok = is_transpilable("for i=1,10 do end", err);
    REQUIRE_FALSE(ok);
    REQUIRE_FALSE(err.empty());
}

// ============================================================================
// emit_sql: select simple
// ============================================================================

TEST_CASE("emit_sql: select simple single table", "[tql_to_sql]") {
    State st = make_state_one_stage();
    std::vector<TableInput> tables = { make_table("sales", {"name", "amount"}) };

    SqlEmit e = emit_sql(st, tables);

    REQUIRE(e.error.empty());
    REQUIRE(e.sql.find("WITH") != std::string::npos);
    REQUIRE(e.sql.find("t0")   != std::string::npos);
    REQUIRE(e.sql.find("\"sales\"") != std::string::npos);
    REQUIRE(e.sql.find("\"name\"")  != std::string::npos);
    REQUIRE(e.sql.find("\"amount\"") != std::string::npos);
    REQUIRE(e.sql.find("SELECT * FROM t0") != std::string::npos);
}

TEST_CASE("emit_sql: filter eq produces WHERE with placeholder", "[tql_to_sql]") {
    State st = make_state_one_stage();
    Filter f;
    f.col   = 0;  // "region"
    f.op    = Op::Eq;
    f.value = "North";
    st.stages[0].filters.push_back(f);

    std::vector<TableInput> tables = { make_table("sales", {"region", "amount"}) };
    SqlEmit e = emit_sql(st, tables);

    REQUIRE(e.error.empty());
    REQUIRE(e.sql.find("WHERE") != std::string::npos);
    REQUIRE(e.sql.find(" = ")   != std::string::npos);
    REQUIRE(e.sql.find("?")     != std::string::npos);
    REQUIRE(e.params.size() == 1);
    REQUIRE(e.params[0] == "North");
}

TEST_CASE("emit_sql: sort produces ORDER BY", "[tql_to_sql]") {
    State st = make_state_one_stage();
    SortClause sc;
    sc.col  = "amount";
    sc.desc = true;
    st.stages[0].sorts.push_back(sc);

    std::vector<TableInput> tables = { make_table("sales", {"region", "amount"}) };
    SqlEmit e = emit_sql(st, tables);

    REQUIRE(e.error.empty());
    REQUIRE(e.sql.find("ORDER BY") != std::string::npos);
    REQUIRE(e.sql.find("DESC")     != std::string::npos);
}

TEST_CASE("emit_sql: group by + sum aggregation produces correct SQL", "[tql_to_sql]") {
    State st;
    // Stage 0: passthrough
    st.stages.push_back(Stage{});
    // Stage 1: breakout + aggregation
    Stage s1;
    s1.breakouts.push_back("region");
    Aggregation ag;
    ag.fn  = AggFn::Sum;
    ag.col = "amount";
    s1.aggregations.push_back(ag);
    st.stages.push_back(s1);
    st.active_stage = 1;

    std::vector<TableInput> tables = { make_table("sales", {"region", "amount"}) };
    SqlEmit e = emit_sql(st, tables, 1);

    REQUIRE(e.error.empty());
    REQUIRE(e.sql.find("GROUP BY") != std::string::npos);
    REQUIRE(e.sql.find("SUM(")     != std::string::npos);
    REQUIRE(e.sql.find("\"region\"") != std::string::npos);
    REQUIRE(e.sql.find("SELECT * FROM t1") != std::string::npos);
}

TEST_CASE("emit_sql: join two tables produces JOIN clause", "[tql_to_sql]") {
    State st = make_state_one_stage();
    Join jn;
    jn.alias    = "cat";
    jn.source   = "categories";
    jn.on.push_back({"cat_id", "id"});
    jn.strategy = JoinStrategy::Left;
    st.joins.push_back(jn);
    st.main_source = "sales";

    std::vector<TableInput> tables = {
        make_table("sales",      {"cat_id", "amount"}),
        make_table("categories", {"id", "name"}),
    };
    SqlEmit e = emit_sql(st, tables);

    REQUIRE(e.error.empty());
    REQUIRE(e.sql.find("LEFT JOIN")    != std::string::npos);
    REQUIRE(e.sql.find("\"categories\"") != std::string::npos);
    REQUIRE(e.sql.find("\"cat\"")      != std::string::npos);
    REQUIRE(e.sql.find(" ON ")         != std::string::npos);
}

TEST_CASE("emit_sql: empty state returns error", "[tql_to_sql]") {
    State st;  // no stages
    std::vector<TableInput> tables = { make_table("t", {"a"}) };
    SqlEmit e = emit_sql(st, tables);
    REQUIRE_FALSE(e.error.empty());
}

TEST_CASE("emit_sql: empty tables returns error", "[tql_to_sql]") {
    State st = make_state_one_stage();
    std::vector<TableInput> tables;
    SqlEmit e = emit_sql(st, tables);
    REQUIRE_FALSE(e.error.empty());
}

TEST_CASE("emit_sql: contains filter produces LIKE with wildcards", "[tql_to_sql]") {
    State st = make_state_one_stage();
    Filter f;
    f.col   = 0;
    f.op    = Op::Contains;
    f.value = "foo";
    st.stages[0].filters.push_back(f);

    std::vector<TableInput> tables = { make_table("t", {"name"}) };
    SqlEmit e = emit_sql(st, tables);

    REQUIRE(e.error.empty());
    REQUIRE(e.sql.find("LIKE") != std::string::npos);
    REQUIRE(e.params.size() == 1);
    REQUIRE(e.params[0] == "%foo%");
}
