package tech.easyflow.ai.node;

import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.datacenter.execution.model.DatasetRef;

import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

public class SearchDatasetNodeTest {

    @Test
    public void testResolveQuerySqlShouldUseNodeQuerySqlAndResolveTemplate() throws Exception {
        DatasetRef datasetRef = new DatasetRef();
        datasetRef.setSourceId(BigInteger.valueOf(1001L));
        SearchDatasetNode node = new SearchDatasetNode(datasetRef, """
                SELECT id, name
                FROM orders_{{biz}}
                WHERE name LIKE '%{{keyword}}%'
                ORDER BY created_at {{direction}}
                """);

        Map<String, Object> params = new HashMap<>();
        params.put("biz", "prod");
        params.put("keyword", "vip");
        params.put("direction", "DESC");

        String sql = invokeResolveQuerySql(node, params);
        Assert.assertEquals("""
                SELECT id, name
                FROM orders_prod
                WHERE name LIKE '%vip%'
                ORDER BY created_at DESC
                """.trim(), sql);
    }

    @Test
    public void testResolveQuerySqlShouldUseNodeQuerySqlWhenParamsDoNotContainSql() throws Exception {
        DatasetRef datasetRef = new DatasetRef();
        datasetRef.setSourceId(BigInteger.valueOf(2002L));
        SearchDatasetNode node = new SearchDatasetNode(datasetRef, "SELECT * FROM fallback_table");

        Map<String, Object> params = new HashMap<>();

        String sql = invokeResolveQuerySql(node, params);
        Assert.assertEquals("SELECT * FROM fallback_table", sql);
    }

    @Test
    public void testResolveQuerySqlShouldRejectBlankSql() throws Exception {
        DatasetRef datasetRef = new DatasetRef();
        datasetRef.setSourceId(BigInteger.valueOf(3003L));
        SearchDatasetNode node = new SearchDatasetNode(datasetRef, "   ");

        try {
            invokeResolveQuerySql(node, new HashMap<>());
            Assert.fail("expected BusinessException");
        } catch (Exception e) {
            Throwable cause = e.getCause() == null ? e : e.getCause();
            Assert.assertTrue(cause instanceof BusinessException);
            Assert.assertEquals("查询数据节点未设置 SQL", cause.getMessage());
        }
    }

    /**
     * 验证查询节点可接收大模型返回的单个 SQL Markdown 代码块。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void testResolveQuerySqlShouldUnwrapMarkdownSqlCodeBlock() throws Exception {
        DatasetRef datasetRef = new DatasetRef();
        datasetRef.setSourceId(BigInteger.valueOf(4004L));
        SearchDatasetNode node = new SearchDatasetNode(datasetRef, "{{query}}");
        Map<String, Object> params = new HashMap<>();
        params.put("query", "```sql SELECT token FROM Sheet1 WHERE col_id = 'deepseek-v4-pro'; ```");

        String sql = invokeResolveQuerySql(node, params);

        Assert.assertEquals("SELECT token FROM Sheet1 WHERE col_id = 'deepseek-v4-pro';", sql);
    }

    /**
     * 验证包含说明文本的模型输出不会被宽松清洗，从而继续由 SQL 解析器拒绝。
     *
     * @throws Exception 反射调用失败时抛出
     */
    @Test
    public void testResolveQuerySqlShouldKeepTextOutsideSqlCodeBlock() throws Exception {
        DatasetRef datasetRef = new DatasetRef();
        datasetRef.setSourceId(BigInteger.valueOf(5005L));
        SearchDatasetNode node = new SearchDatasetNode(datasetRef, "{{query}}");
        Map<String, Object> params = new HashMap<>();
        String generated = "查询语句如下：\n```sql\nSELECT token FROM Sheet1;\n```";
        params.put("query", generated);

        String sql = invokeResolveQuerySql(node, params);

        Assert.assertEquals(generated, sql);
    }

    private String invokeResolveQuerySql(SearchDatasetNode node, Map<String, Object> params) throws Exception {
        Method method = SearchDatasetNode.class.getDeclaredMethod("resolveQuerySql", Map.class);
        method.setAccessible(true);
        return (String) method.invoke(node, params);
    }
}
