package tech.easyflow.datacenter.connector.support;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.row.Row;
import org.springframework.util.CollectionUtils;
import tech.easyflow.common.constant.enums.EnumFieldType;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.datacenter.connector.DatacenterConnector;
import tech.easyflow.datacenter.connector.SqlDialect;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.execution.model.DatacenterConnectionTestResult;
import tech.easyflow.datacenter.execution.model.DatacenterQueryFilter;
import tech.easyflow.datacenter.execution.model.DatacenterQueryRequest;
import tech.easyflow.datacenter.execution.model.DatacenterQuerySort;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.enums.DatacenterCapability;
import tech.easyflow.datacenter.meta.enums.DatacenterConnectionErrorCode;
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
import tech.easyflow.datacenter.meta.enums.DatacenterTableKind;
import tech.easyflow.datacenter.meta.model.DatacenterCatalogMeta;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

public abstract class AbstractJdbcConnector implements DatacenterConnector {

    private final DatacenterSourceType sourceType;
    private final SqlDialect sqlDialect;
    private final Set<DatacenterCapability> capabilities;

    protected AbstractJdbcConnector(DatacenterSourceType sourceType, SqlDialect sqlDialect, Set<DatacenterCapability> capabilities) {
        this.sourceType = sourceType;
        this.sqlDialect = sqlDialect;
        this.capabilities = capabilities;
    }

    @Override
    public DatacenterSourceType getSourceType() {
        return sourceType;
    }

    @Override
    public Set<DatacenterCapability> getCapabilities() {
        return capabilities;
    }

    protected abstract <T> T withConnection(DatacenterSource source, boolean cacheable, JdbcCallback<T> callback) throws Exception;

    @Override
    public DatacenterConnectionTestResult testConnection(DatacenterSource source) {
        DatacenterConnectionTestResult result = new DatacenterConnectionTestResult();
        result.setCapabilities(capabilities.stream().map(Enum::name).toList());
        if (StrUtil.isBlank(source.getJdbcUrl()) && requiresJdbcUrl()) {
            result.setSuccess(false);
            result.setErrorCode(DatacenterConnectionErrorCode.INVALID_ARGUMENT.name());
            result.setMessage("缺少 JDBC URL");
            return result;
        }
        try {
            if (StrUtil.isNotBlank(source.getDriverClassName())) {
                Class.forName(source.getDriverClassName());
            }
            Map<String, Object> details = withConnection(source, false, connection -> {
                DatabaseMetaData metaData = connection.getMetaData();
                Map<String, Object> map = new LinkedHashMap<>();
                map.put("databaseProductName", metaData.getDatabaseProductName());
                map.put("databaseProductVersion", metaData.getDatabaseProductVersion());
                map.put("url", metaData.getURL());
                return map;
            });
            result.setSuccess(true);
            result.setMessage("连接成功");
            result.setDetails(details);
            return result;
        } catch (ClassNotFoundException ex) {
            result.setSuccess(false);
            result.setErrorCode(DatacenterConnectionErrorCode.DRIVER_NOT_FOUND.name());
            result.setMessage(ex.getMessage());
            return result;
        } catch (SQLException ex) {
            result.setSuccess(false);
            result.setErrorCode(mapSqlError(ex));
            result.setMessage(ex.getMessage());
            return result;
        } catch (Exception ex) {
            result.setSuccess(false);
            result.setErrorCode(DatacenterConnectionErrorCode.UNKNOWN_ERROR.name());
            result.setMessage(ex.getMessage());
            return result;
        }
    }

    @Override
    public List<DatacenterCatalogMeta> listCatalogs(DatacenterSource source) {
        try {
            return withConnection(source, true, connection -> {
                List<DatacenterCatalogMeta> result = new ArrayList<>();
                DatabaseMetaData metaData = connection.getMetaData();
                try (ResultSet catalogs = metaData.getCatalogs()) {
                    while (catalogs.next()) {
                        String name = catalogs.getString("TABLE_CAT");
                        if (StrUtil.isBlank(name)) {
                            continue;
                        }
                        DatacenterCatalogMeta meta = new DatacenterCatalogMeta();
                        meta.setSourceId(source.getId());
                        meta.setCatalogName(name);
                        meta.setCatalogType("CATALOG");
                        result.add(meta);
                    }
                }
                try (ResultSet schemas = metaData.getSchemas()) {
                    while (schemas.next()) {
                        String name = schemas.getString("TABLE_SCHEM");
                        if (StrUtil.isBlank(name) || containsCatalog(result, name)) {
                            continue;
                        }
                        DatacenterCatalogMeta meta = new DatacenterCatalogMeta();
                        meta.setSourceId(source.getId());
                        meta.setCatalogName(name);
                        meta.setCatalogType("SCHEMA");
                        result.add(meta);
                    }
                }
                result = filterConfiguredCatalogs(source, result);
                if (result.isEmpty()) {
                    String fallback = resolveCatalogName(source, null);
                    if (StrUtil.isNotBlank(fallback)) {
                        DatacenterCatalogMeta meta = new DatacenterCatalogMeta();
                        meta.setSourceId(source.getId());
                        meta.setCatalogName(fallback);
                        meta.setCatalogType("DEFAULT");
                        result.add(meta);
                    }
                }
                return result;
            });
        } catch (Exception ex) {
            throw DatacenterConnectorExceptionSupport.wrapAccessException("读取目录失败", ex);
        }
    }

    @Override
    public List<DatacenterTable> listTables(DatacenterSource source, String catalogName) {
        try {
            return withConnection(source, true, connection -> {
                DatabaseMetaData metaData = connection.getMetaData();
                List<DatacenterTable> tables = new ArrayList<>();
                try (ResultSet resultSet = metaData.getTables(resolveCatalogArgument(source, catalogName), resolveSchemaArgument(source, catalogName), "%", new String[]{"TABLE", "VIEW"})) {
                    while (resultSet.next()) {
                        DatacenterTable table = new DatacenterTable();
                        table.setSourceId(source.getId());
                        table.setTableName(resultSet.getString("TABLE_NAME"));
                        table.setTableDesc(resultSet.getString("REMARKS"));
                        table.setActualTable(resultSet.getString("TABLE_NAME"));
                        table.setMaterializedTable(resultSet.getString("TABLE_NAME"));
                        table.setAccessMode(capabilities.contains(DatacenterCapability.WRITE_MUTATION) ? "READ_WRITE" : "READ_ONLY");
                        table.setTableKind(resolveTableKind(resultSet.getString("TABLE_TYPE")).name());
                        table.setCapabilitiesJson(Map.of("capabilities", capabilities.stream().map(Enum::name).toList()));
                        tables.add(table);
                    }
                }
                return tables;
            });
        } catch (Exception ex) {
            throw DatacenterConnectorExceptionSupport.wrapAccessException("读取表列表失败", ex);
        }
    }

    @Override
    public DatacenterTableDetailMeta getTableDetail(DatacenterSource source, String catalogName, String tableName) {
        try {
            return withConnection(source, true, connection -> {
                DatabaseMetaData metaData = connection.getMetaData();
                DatacenterTableDetailMeta detail = new DatacenterTableDetailMeta();
                DatacenterTable table = new DatacenterTable();
                table.setSourceId(source.getId());
                table.setTableName(tableName);
                table.setActualTable(tableName);
                table.setMaterializedTable(tableName);
                table.setAccessMode(capabilities.contains(DatacenterCapability.WRITE_MUTATION) ? "READ_WRITE" : "READ_ONLY");
                table.setTableKind(DatacenterTableKind.EXTERNAL_TABLE.name());
                table.setCapabilitiesJson(Map.of("capabilities", capabilities.stream().map(Enum::name).toList()));
                detail.setTable(table);

                try (ResultSet tableSet = metaData.getTables(
                        resolveCatalogArgument(source, catalogName),
                        resolveSchemaArgument(source, catalogName),
                        tableName,
                        new String[]{"TABLE", "VIEW"})) {
                    while (tableSet.next()) {
                        String currentTableName = tableSet.getString("TABLE_NAME");
                        if (!matchesTableName(currentTableName, tableName)) {
                            continue;
                        }
                        table.setTableDesc(tableSet.getString("REMARKS"));
                        table.setTableKind(resolveTableKind(tableSet.getString("TABLE_TYPE")).name());
                        break;
                    }
                }

                Set<String> primaryKeys = new HashSet<>();
                try (ResultSet pkSet = metaData.getPrimaryKeys(resolveCatalogArgument(source, catalogName), resolveSchemaArgument(source, catalogName), tableName)) {
                    while (pkSet.next()) {
                        primaryKeys.add(pkSet.getString("COLUMN_NAME"));
                    }
                }

                List<DatacenterTableField> fields = new ArrayList<>();
                try (ResultSet columns = metaData.getColumns(resolveCatalogArgument(source, catalogName), resolveSchemaArgument(source, catalogName), tableName, "%")) {
                    while (columns.next()) {
                        DatacenterTableField field = new DatacenterTableField();
                        field.setFieldName(columns.getString("COLUMN_NAME"));
                        field.setSourceColumnName(columns.getString("COLUMN_NAME"));
                        field.setFieldDesc(columns.getString("REMARKS"));
                        field.setJdbcType(columns.getString("TYPE_NAME"));
                        field.setPrecision(columns.getInt("COLUMN_SIZE"));
                        field.setScale(columns.getInt("DECIMAL_DIGITS"));
                        field.setRequired(columns.getInt("NULLABLE") == DatabaseMetaData.columnNoNulls ? 1 : 0);
                        field.setQueryable(1);
                        field.setSortable(1);
                        field.setWritable(capabilities.contains(DatacenterCapability.WRITE_MUTATION) ? 1 : 0);
                        field.setIndexed(primaryKeys.contains(field.getFieldName()) ? 1 : 0);
                        field.setFieldType(mapFieldType(columns.getInt("DATA_TYPE")));
                        fields.add(field);
                    }
                }
                detail.setFields(fields);
                return detail;
            });
        } catch (Exception ex) {
            throw DatacenterConnectorExceptionSupport.wrapAccessException("读取表详情失败", ex);
        }
    }

    @Override
    public Page<Row> queryPage(DatacenterSource source, DatacenterTable table, DatacenterQueryRequest request) {
        if (!capabilities.contains(DatacenterCapability.READ_QUERY)) {
            throw new BusinessException("当前数据源暂不支持查询");
        }
        try {
            return withConnection(source, true, connection -> doQueryPage(connection, source, table, request));
        } catch (Exception ex) {
            throw DatacenterConnectorExceptionSupport.wrapAccessException("查询失败", ex);
        }
    }

    @Override
    public List<Row> queryBySql(DatacenterSource source, String sql) {
        if (!capabilities.contains(DatacenterCapability.READ_QUERY)) {
            throw new BusinessException("当前数据源暂不支持查询");
        }
        try {
            return withConnection(source, true, connection -> doQueryBySql(connection, sql));
        } catch (Exception ex) {
            throw DatacenterConnectorExceptionSupport.wrapAccessException("SQL 查询失败", ex);
        }
    }

    @Override
    public void saveRow(DatacenterSource source, DatacenterTable table, JSONObject data, LoginAccount account) {
        throw new BusinessException("当前数据源不支持写入");
    }

    @Override
    public void deleteRow(DatacenterSource source, DatacenterTable table, BigInteger id, LoginAccount account) {
        throw new BusinessException("当前数据源不支持删除");
    }

    protected boolean requiresJdbcUrl() {
        return true;
    }

    protected Page<Row> doQueryPage(Connection connection, DatacenterSource source, DatacenterTable table, DatacenterQueryRequest request) throws SQLException {
        List<Object> params = new ArrayList<>();
        String selectColumns = CollectionUtils.isEmpty(request.getSelectedColumns())
                ? "*"
                : request.getSelectedColumns().stream().map(sqlDialect::quoteIdentifier).collect(Collectors.joining(", "));
        String qualifiedTable = sqlDialect.qualifyTable(resolveCatalogName(source, request.getDatasetRef() == null ? null : request.getDatasetRef().getCatalogName()), resolvePhysicalTableName(table));
        StringBuilder whereClause = new StringBuilder();
        if (StrUtil.isNotBlank(request.getWhere())) {
            whereClause.append(" WHERE ").append(request.getWhere());
        } else if (!CollectionUtils.isEmpty(request.getFilters())) {
            whereClause.append(" WHERE 1=1 ");
            for (DatacenterQueryFilter filter : request.getFilters()) {
                appendFilter(whereClause, params, filter);
            }
        }
        String orderClause = buildOrderClause(request.getSorts());
        String baseSql = "SELECT " + selectColumns + " FROM " + qualifiedTable + whereClause + orderClause;
        String countSql = "SELECT COUNT(1) FROM " + qualifiedTable + whereClause;
        long total = queryCount(connection, countSql, params);
        if (total == 0L) {
            return new Page<>(new ArrayList<>(), request.getPageNumber(), request.getPageSize(), total);
        }
        String pageSql = sqlDialect.buildPageSql(baseSql, request.getPageNumber(), request.getPageSize());
        List<Object> pageParams = new ArrayList<>(params);
        if (pageSql.contains("FETCH NEXT") || pageSql.toLowerCase(Locale.ROOT).contains("limit")) {
            pageParams.add((request.getPageNumber() - 1) * request.getPageSize());
            pageParams.add(request.getPageSize());
        }
        List<Row> records = new ArrayList<>();
        try (PreparedStatement statement = connection.prepareStatement(pageSql)) {
            bindParameters(statement, pageParams);
            try (ResultSet resultSet = statement.executeQuery()) {
                ResultSetMetaData metaData = resultSet.getMetaData();
                while (resultSet.next()) {
                    Row row = new Row();
                    for (int i = 1; i <= metaData.getColumnCount(); i++) {
                        String columnLabel = metaData.getColumnLabel(i);
                        row.put(columnLabel, normalizeValue(resultSet.getObject(i)));
                    }
                    records.add(row);
                }
            }
        }
        return new Page<>(records, request.getPageNumber(), request.getPageSize(), total);
    }

    protected String resolvePhysicalTableName(DatacenterTable table) {
        return StrUtil.blankToDefault(table.getActualTable(), table.getTableName());
    }

    protected List<Row> doQueryBySql(Connection connection, String sql) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(sql);
             ResultSet resultSet = statement.executeQuery()) {
            return readRows(resultSet);
        }
    }

    protected String resolveCatalogName(DatacenterSource source, String requestedCatalogName) {
        if (StrUtil.isNotBlank(requestedCatalogName)) {
            return requestedCatalogName;
        }
        if (usesCatalogNamespace()) {
            return source.getDatabaseName();
        }
        return source.getSchemaName();
    }

    protected List<Row> readRows(ResultSet resultSet) throws SQLException {
        List<Row> records = new ArrayList<>();
        ResultSetMetaData metaData = resultSet.getMetaData();
        while (resultSet.next()) {
            Row row = new Row();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                String columnLabel = metaData.getColumnLabel(i);
                row.put(columnLabel, normalizeValue(resultSet.getObject(i)));
            }
            records.add(row);
        }
        return records;
    }

    protected String resolveCatalogArgument(DatacenterSource source, String catalogName) {
        return usesCatalogNamespace() ? resolveCatalogName(source, catalogName) : source.getDatabaseName();
    }

    protected String resolveSchemaArgument(DatacenterSource source, String catalogName) {
        return usesCatalogNamespace() ? source.getSchemaName() : resolveCatalogName(source, catalogName);
    }

    protected boolean usesCatalogNamespace() {
        return sourceType == DatacenterSourceType.MYSQL
                || sourceType == DatacenterSourceType.PROJECT_MYSQL
                || sourceType == DatacenterSourceType.GBASE_8A
                || sourceType == DatacenterSourceType.GBASE_8S;
    }

    private List<DatacenterCatalogMeta> filterConfiguredCatalogs(DatacenterSource source, List<DatacenterCatalogMeta> items) {
        if (items == null || items.isEmpty() || source == null) {
            return items;
        }
        String configuredName = usesCatalogNamespace()
                ? StrUtil.trimToNull(source.getDatabaseName())
                : StrUtil.trimToNull(source.getSchemaName());
        if (StrUtil.isBlank(configuredName)) {
            return items;
        }
        List<DatacenterCatalogMeta> matched = items.stream()
                .filter(item -> configuredName.equalsIgnoreCase(item.getCatalogName()))
                .collect(Collectors.toList());
        return matched.isEmpty() ? items : matched;
    }

    private boolean containsCatalog(List<DatacenterCatalogMeta> items, String catalogName) {
        return items.stream().anyMatch(item -> catalogName.equalsIgnoreCase(item.getCatalogName()));
    }

    private DatacenterTableKind resolveTableKind(String tableType) {
        return "VIEW".equalsIgnoreCase(tableType) ? DatacenterTableKind.EXTERNAL_VIEW : DatacenterTableKind.EXTERNAL_TABLE;
    }

    private boolean matchesTableName(String currentTableName, String targetTableName) {
        if (currentTableName == null || targetTableName == null) {
            return false;
        }
        return currentTableName.equals(targetTableName)
                || currentTableName.equalsIgnoreCase(targetTableName);
    }

    private Integer mapFieldType(int jdbcType) {
        return switch (jdbcType) {
            case Types.INTEGER, Types.TINYINT, Types.SMALLINT, Types.BIGINT -> EnumFieldType.INTEGER.getCode();
            case Types.FLOAT, Types.DOUBLE, Types.REAL, Types.NUMERIC, Types.DECIMAL -> EnumFieldType.NUMBER.getCode();
            case Types.TIMESTAMP, Types.DATE, Types.TIME -> EnumFieldType.TIME.getCode();
            case Types.BOOLEAN, Types.BIT -> EnumFieldType.BOOLEAN.getCode();
            default -> EnumFieldType.STRING.getCode();
        };
    }

    private void appendFilter(StringBuilder sql, List<Object> params, DatacenterQueryFilter filter) {
        String operator = StrUtil.blankToDefault(filter.getOperator(), "EQ").toUpperCase(Locale.ROOT);
        String column = sqlDialect.quoteIdentifier(filter.getColumn());
        switch (operator) {
            case "EQ" -> {
                sql.append(" AND ").append(column).append(" = ?");
                params.add(filter.getValue());
            }
            case "LIKE" -> {
                sql.append(" AND ").append(column).append(" LIKE ?");
                params.add("%" + filter.getValue() + "%");
            }
            case "GT" -> {
                sql.append(" AND ").append(column).append(" > ?");
                params.add(filter.getValue());
            }
            case "GTE" -> {
                sql.append(" AND ").append(column).append(" >= ?");
                params.add(filter.getValue());
            }
            case "LT" -> {
                sql.append(" AND ").append(column).append(" < ?");
                params.add(filter.getValue());
            }
            case "LTE" -> {
                sql.append(" AND ").append(column).append(" <= ?");
                params.add(filter.getValue());
            }
            case "IN" -> {
                List<Object> values = filter.getValues() == null ? List.of() : filter.getValues();
                if (values.isEmpty()) {
                    sql.append(" AND 1 = 0");
                } else {
                    sql.append(" AND ").append(column).append(" IN (");
                    sql.append(values.stream().map(item -> "?").collect(Collectors.joining(",")));
                    sql.append(")");
                    params.addAll(values);
                }
            }
            case "IS_NULL" -> sql.append(" AND ").append(column).append(" IS NULL");
            default -> throw new BusinessException("不支持的过滤操作: " + operator);
        }
    }

    private String buildOrderClause(List<DatacenterQuerySort> sorts) {
        if (CollectionUtils.isEmpty(sorts)) {
            return "";
        }
        return " ORDER BY " + sorts.stream()
                .map(sort -> sqlDialect.quoteIdentifier(sort.getColumn()) + " " + ("DESC".equalsIgnoreCase(sort.getDirection()) ? "DESC" : "ASC"))
                .collect(Collectors.joining(", "));
    }

    private long queryCount(Connection connection, String sql, List<Object> params) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            bindParameters(statement, params);
            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    return resultSet.getLong(1);
                }
                return 0L;
            }
        }
    }

    private void bindParameters(PreparedStatement statement, List<Object> params) throws SQLException {
        for (int i = 0; i < params.size(); i++) {
            statement.setObject(i + 1, params.get(i));
        }
    }

    private Object normalizeValue(Object value) {
        if (value instanceof BigDecimal || value instanceof BigInteger || value instanceof Long) {
            return value.toString();
        }
        return value;
    }

    private String mapSqlError(SQLException ex) {
        String state = ex.getSQLState();
        String message = ex.getMessage() == null ? "" : ex.getMessage().toLowerCase(Locale.ROOT);
        if (message.contains("access denied") || message.contains("password") || message.contains("authentication")) {
            return DatacenterConnectionErrorCode.AUTH_FAILED.name();
        }
        if (message.contains("unknown database") || message.contains("database does not exist")) {
            return DatacenterConnectionErrorCode.DATABASE_NOT_FOUND.name();
        }
        if (message.contains("schema") && message.contains("does not exist")) {
            return DatacenterConnectionErrorCode.SCHEMA_NOT_FOUND.name();
        }
        if (message.contains("permission denied") || message.contains("insufficient privilege")) {
            return DatacenterConnectionErrorCode.PERMISSION_DENIED.name();
        }
        if (state != null && state.startsWith("08")) {
            return DatacenterConnectionErrorCode.NETWORK_UNREACHABLE.name();
        }
        return DatacenterConnectionErrorCode.UNKNOWN_ERROR.name();
    }

    @FunctionalInterface
    protected interface JdbcCallback<T> {
        T apply(Connection connection) throws Exception;
    }
}
