package com.easyagents.federation.sql.runtime;

import com.easyagents.federation.sql.federation.FederationExecutionPolicy;
import com.easyagents.federation.sql.federation.FederationLogicalTableDefinition;
import com.easyagents.federation.sql.federation.FederationQueryScopeDefinition;
import com.easyagents.federation.sql.federation.FederationSourceBindingDefinition;
import com.easyagents.federation.sql.source.SourceId;
import java.util.List;
import java.util.Map;
import org.apache.calcite.config.Lex;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.junit.Assert;
import org.junit.Test;

/**
 * 逻辑表到物理 Binding 表路径的 Calcite AST 解析测试。
 */
public class LogicalTableSqlResolverTest {

    /**
     * 验证短逻辑表名保留为查询限定别名，并指向实际物理表。
     */
    @Test
    public void shouldResolveShortNameAndPreserveLogicalQualifier() {
        String sql = resolve("SELECT outlet.ID FROM outlet");

        Assert.assertTrue(sql, sql.contains("MYSQL_1.MAIN.physical_outlet AS OUTLET"));
        Assert.assertTrue(sql, sql.toUpperCase().contains("OUTLET.ID"));
    }

    /**
     * 验证三段逻辑表名与四段列限定名映射到同一物理表。
     */
    @Test
    public void shouldResolveQualifiedLogicalTableName() {
        String sql = resolve(
            "SELECT MYSQL_1.MAIN.outlet.ID FROM MYSQL_1.MAIN.outlet"
        );

        Assert.assertTrue(sql, sql.contains("MYSQL_1.MAIN.physical_outlet AS OUTLET"));
        Assert.assertTrue(sql, sql.toUpperCase().contains("OUTLET.ID"));
    }

    /**
     * 验证用户显式 SQL 别名不会与内置逻辑别名形成嵌套 AS。
     */
    @Test
    public void shouldPreserveExplicitAlias() {
        String sql = resolve("SELECT o.ID FROM outlet AS o");

        Assert.assertTrue(
            sql,
            sql.toUpperCase().contains("MYSQL_1.MAIN.PHYSICAL_OUTLET AS O")
        );
        Assert.assertFalse(sql, sql.contains("AS outlet AS o"));
    }

    /**
     * 验证逻辑表映射变化会使查询范围 checksum 失效。
     */
    @Test
    public void shouldIncludeLogicalTablesInScopeChecksum() {
        FederationQueryScopeDefinition original = scope("physical_outlet");
        FederationQueryScopeDefinition renamedPhysical = scope("physical_outlet_v2");

        Assert.assertNotEquals(original.checksum(), renamedPhysical.checksum());
    }

    /**
     * 解析并重写测试 SQL。
     *
     * @param sql 测试 SQL
     * @return 重写后的 SQL
     */
    private String resolve(String sql) {
        try {
            SqlNode parsed = SqlParser.create(
                sql,
                SqlParser.config().withLex(Lex.ORACLE)
            ).parseQuery();
            SqlNode resolved = LogicalTableSqlResolver.resolve(parsed, scope("physical_outlet"));
            return resolved.toSqlString(config -> config.withQuoteAllIdentifiers(false)).getSql();
        } catch (SqlParseException exception) {
            throw new AssertionError(exception);
        }
    }

    /**
     * 创建包含单张逻辑表的测试查询范围。
     *
     * @param sourceTableName 物理表名
     * @return 测试查询范围
     */
    private FederationQueryScopeDefinition scope(String sourceTableName) {
        return FederationQueryScopeDefinition.virtual(
            "logical-table-test",
            1,
            Map.of(
                "MYSQL_1",
                FederationSourceBindingDefinition.of(new SourceId("mysql"), 1)
            ),
            "MYSQL_1",
            List.of(FederationLogicalTableDefinition.of(
                "outlet",
                "MYSQL_1",
                "MAIN",
                sourceTableName
            )),
            FederationExecutionPolicy.basic()
        );
    }
}
