package com.easyagents.federation.sql.adapter.jdbc;

import com.easyagents.federation.sql.adapter.AdapterCompatibilityStatus;
import com.easyagents.federation.sql.adapter.AdapterDialectContext;
import com.easyagents.federation.sql.adapter.AdapterHints;
import com.easyagents.federation.sql.source.FederationSourceDefinition;
import com.easyagents.federation.sql.source.SourceId;
import java.lang.reflect.Proxy;
import java.sql.DatabaseMetaData;
import java.util.List;
import java.util.Map;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.dialect.MysqlSqlDialect;
import org.apache.calcite.sql.dialect.OracleSqlDialect;
import org.apache.calcite.sql.dialect.PostgresqlSqlDialect;
import org.junit.Assert;
import org.junit.Test;

/**
 * 默认 JDBC Adapter 的数据库识别与 Calcite 方言选择契约测试。
 */
public class JdbcDialectSelectionTest {

    /**
     * 验证 MySQL、PostgreSQL 和 Oracle 使用对应 Calcite 官方方言。
     *
     * @throws Exception 元数据读取失败
     */
    @Test
    public void shouldSelectBuiltInCalciteDialects() throws Exception {
        JdbcFederationSqlAdapterProvider adapter = new JdbcFederationSqlAdapterProvider();
        SqlDialect mysql = assertDialect(adapter, "MySQL", "`", MysqlSqlDialect.class);
        SqlDialect postgresql = assertDialect(adapter, "PostgreSQL", "\"", PostgresqlSqlDialect.class);
        assertDialect(adapter, "Oracle", "\"", OracleSqlDialect.class);
        Assert.assertEquals(mysql.isCaseSensitive(), adapter.parserConfig(mysql).caseSensitive());
        Assert.assertTrue(adapter.parserConfig(postgresql).caseSensitive());
    }

    /**
     * 验证未知数据库默认拒绝，显式 ANSI 模式才以未验证状态放行。
     *
     * @throws Exception 元数据读取失败
     */
    @Test
    public void shouldRequireExplicitAnsiModeForUnknownDatabase() throws Exception {
        JdbcFederationSqlAdapterProvider adapter = new JdbcFederationSqlAdapterProvider();
        DatabaseMetaData metadata = metadata("UnknownDB", "\"");
        Assert.assertFalse(adapter.supports(metadata, new AdapterHints(Map.of())));
        AdapterHints experimental = new AdapterHints(Map.of(
            JdbcFederationSqlAdapterProvider.EXPERIMENTAL_ANSI_OPTION,
            "true"
        ));
        Assert.assertTrue(adapter.supports(metadata, experimental));
        Assert.assertEquals(
            AdapterCompatibilityStatus.CODE_SUPPORTED_UNVERIFIED,
            adapter.compatibility(metadata, experimental).status()
        );
    }

    private static SqlDialect assertDialect(
        JdbcFederationSqlAdapterProvider adapter,
        String product,
        String quote,
        Class<? extends SqlDialect> expectedType
    ) throws Exception {
        DatabaseMetaData metadata = metadata(product, quote);
        Assert.assertTrue(adapter.supports(metadata, new AdapterHints(Map.of())));
        SqlDialect dialect = adapter.createDialect(new AdapterDialectContext(metadata, definition(Map.of())));
        Assert.assertTrue(expectedType.isInstance(dialect));
        Assert.assertEquals(
            AdapterCompatibilityStatus.CODE_SUPPORTED_UNVERIFIED,
            adapter.compatibility(metadata, new AdapterHints(Map.of())).status()
        );
        return dialect;
    }

    private static FederationSourceDefinition definition(Map<String, String> options) {
        return new FederationSourceDefinition(
            new SourceId("source"),
            1,
            JdbcFederationSqlAdapterProvider.ADAPTER_ID,
            List.of(new JdbcSchemaDefinition("app", null, null)),
            options
        );
    }

    private static DatabaseMetaData metadata(String product, String quote) {
        return (DatabaseMetaData) Proxy.newProxyInstance(
            JdbcDialectSelectionTest.class.getClassLoader(),
            new Class<?>[] {DatabaseMetaData.class},
            (proxy, method, arguments) -> switch (method.getName()) {
                case "getDatabaseProductName" -> product;
                case "getDatabaseProductVersion" -> "test-version";
                case "getDatabaseMajorVersion" -> 1;
                case "getDatabaseMinorVersion" -> 0;
                case "getDriverName" -> "test-driver";
                case "getDriverVersion" -> "1";
                case "getIdentifierQuoteString" -> quote;
                case "nullsAreSortedHigh" -> true;
                case "nullsAreSortedAtEnd", "nullsAreSortedAtStart", "nullsAreSortedLow" -> false;
                case "storesUpperCaseIdentifiers", "storesUpperCaseQuotedIdentifiers" -> false;
                case "storesLowerCaseIdentifiers", "storesLowerCaseQuotedIdentifiers" -> false;
                case "storesMixedCaseIdentifiers", "storesMixedCaseQuotedIdentifiers" -> true;
                case "supportsMixedCaseIdentifiers", "supportsMixedCaseQuotedIdentifiers" -> true;
                default -> defaultValue(method.getReturnType());
            }
        );
    }

    private static Object defaultValue(Class<?> type) {
        if (!type.isPrimitive()) {
            return null;
        }
        if (type == boolean.class) {
            return false;
        }
        if (type == int.class) {
            return 0;
        }
        if (type == long.class) {
            return 0L;
        }
        if (type == short.class) {
            return (short) 0;
        }
        if (type == byte.class) {
            return (byte) 0;
        }
        if (type == float.class) {
            return 0F;
        }
        if (type == double.class) {
            return 0D;
        }
        if (type == char.class) {
            return '\0';
        }
        return null;
    }
}
