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.query.QueryWrapper;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.row.RowKey;
import org.springframework.util.CollectionUtils;
import org.springframework.dao.DuplicateKeyException;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.datacenter.connector.DatacenterConnector;
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.DatacenterQueryRequest;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.enums.DatacenterCapability;
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
import tech.easyflow.datacenter.meta.model.DatacenterCatalogMeta;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;

import javax.sql.DataSource;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.*;
import java.util.function.Consumer;

public abstract class AbstractInternalTableConnector implements DatacenterConnector {

    private static final String WRITE_RECEIPT_TABLE = "tb_datacenter_write_receipt";
    private final DatacenterSourceType sourceType;
    private final Set<DatacenterCapability> capabilities;
    private final DataSource dataSource;

    protected AbstractInternalTableConnector(
            DatacenterSourceType sourceType,
            Set<DatacenterCapability> capabilities,
            DataSource dataSource) {
        this.sourceType = sourceType;
        this.capabilities = capabilities;
        this.dataSource = dataSource;
    }

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

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

    @Override
    public DatacenterConnectionTestResult testConnection(DatacenterSource source) {
        DatacenterConnectionTestResult result = new DatacenterConnectionTestResult();
        result.setSuccess(true);
        result.setMessage("连接成功");
        result.setCapabilities(capabilities.stream().map(Enum::name).toList());
        result.setDetails(Map.of("sourceType", sourceType.name()));
        return result;
    }

    @Override
    public List<DatacenterCatalogMeta> listCatalogs(DatacenterSource source) {
        return Collections.emptyList();
    }

    @Override
    public List<DatacenterTable> listTables(DatacenterSource source, String catalogName) {
        return Collections.emptyList();
    }

    @Override
    public DatacenterTableDetailMeta getTableDetail(DatacenterSource source, String catalogName, String tableName) {
        throw new BusinessException("内部数据源请从元数据注册表读取表结构");
    }

    @Override
    public Page<Row> queryPage(DatacenterSource source, DatacenterTable table, DatacenterQueryRequest request) {
        String actualTable = resolveTableName(table);
        long count = Db.selectCountByQuery(
                actualTable, createQueryWrapper(request.getWhere()));
        if (count == 0) {
            return new Page<>(new ArrayList<>(), request.getPageNumber(), request.getPageSize(), count);
        }
        // selectCountByQuery 会把无投影的 QueryWrapper 改为 COUNT(*)，分页查询必须使用独立实例。
        Page<Row> page = Db.paginate(
                actualTable,
                new Page<>(request.getPageNumber(), request.getPageSize(), count),
                createQueryWrapper(request.getWhere()));
        normalizeRows(page.getRecords());
        return page;
    }

    /**
     * 创建用于动态表查询的独立条件包装器。
     *
     * @param where 已校验的筛选表达式
     * @return 新建的查询条件包装器
     */
    static QueryWrapper createQueryWrapper(String where) {
        QueryWrapper wrapper = QueryWrapper.create();
        if (StrUtil.isNotBlank(where)) {
            wrapper.where(where);
        }
        return wrapper;
    }

    @Override
    public List<Row> queryBySql(DatacenterSource source, String sql) {
        List<Row> rows = Db.selectListBySql(sql);
        normalizeRows(rows);
        return rows;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void consumeBySql(
            DatacenterSource source,
            String sql,
            int fetchSize,
            Consumer<Row> consumer) {
        if (fetchSize <= 0 || consumer == null) {
            throw new IllegalArgumentException(
                    "fetchSize and consumer must be valid");
        }
        try (Connection connection =
                     dataSource.getConnection();
             PreparedStatement statement =
                     connection.prepareStatement(
                             sql,
                             ResultSet.TYPE_FORWARD_ONLY,
                             ResultSet.CONCUR_READ_ONLY)) {
            // 内部数据源使用项目 MySQL，启用驱动前向流式结果。
            statement.setFetchSize(Integer.MIN_VALUE);
            int timeoutSeconds = Integer.getInteger(
                    "easyflow.datacenter.query.timeout-seconds",
                    300);
            if (timeoutSeconds > 0) {
                statement.setQueryTimeout(timeoutSeconds);
            }
            try (ResultSet resultSet =
                         statement.executeQuery()) {
                ResultSetMetaData metaData =
                        resultSet.getMetaData();
                while (resultSet.next()) {
                    Row row = new Row();
                    for (int index = 1;
                         index <= metaData.getColumnCount();
                         index++) {
                        row.put(
                                metaData.getColumnLabel(index),
                                normalizeValue(
                                        resultSet.getObject(index)));
                    }
                    consumer.accept(row);
                }
            }
        } catch (Exception error) {
            throw DatacenterConnectorExceptionSupport
                    .wrapAccessException(
                            "SQL 流式查询失败", error);
        }
    }

    @Override
    public void saveRow(DatacenterSource source, DatacenterTable table, JSONObject data, LoginAccount account) {
        List<DatacenterTableField> fields = table.getFields();
        if (CollectionUtils.isEmpty(fields)) {
            throw new BusinessException("数据集字段为空，无法写入");
        }
        String actualTable = resolveTableName(table);
        RowMutation mutation = buildRowMutation(fields, data, account);
        if (mutation.insert) {
            Db.insert(actualTable, mutation.row);
        } else {
            Db.updateById(actualTable, mutation.row);
        }
    }

    /**
     * 使用 MyBatis-Flex 动态表批处理保存数据行。
     *
     * @param source 数据源
     * @param table 数据表
     * @param rows 待保存数据行
     * @param account 当前操作账号
     * @param batchSize 单批最大行数
     */
    @Override
    public void saveRows(DatacenterSource source,
                         DatacenterTable table,
                         List<JSONObject> rows,
                         LoginAccount account,
                         int batchSize) {
        List<DatacenterTableField> fields = table.getFields();
        if (CollectionUtils.isEmpty(fields)) {
            throw new BusinessException("数据集字段为空，无法写入");
        }
        if (rows == null || rows.isEmpty()) {
            return;
        }
        String actualTable = resolveTableName(table);
        int effectiveBatchSize = Math.max(1, batchSize);
        List<Row> batch = new ArrayList<>(Math.min(rows.size(), effectiveBatchSize));
        Boolean insertBatch = null;
        for (JSONObject data : rows) {
            RowMutation mutation = buildRowMutation(fields, data, account);
            if (insertBatch != null
                    && (insertBatch != mutation.insert || batch.size() >= effectiveBatchSize)) {
                executeRowBatch(actualTable, batch, insertBatch);
                batch.clear();
            }
            insertBatch = mutation.insert;
            batch.add(mutation.row);
        }
        if (!batch.isEmpty()) {
            executeRowBatch(actualTable, batch, Boolean.TRUE.equals(insertBatch));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean saveRowsIdempotently(
            DatacenterSource source,
            DatacenterTable table,
            List<JSONObject> rows,
            LoginAccount account,
            int batchSize,
            String receiptKey,
            String payloadHash) {
        if (StrUtil.isBlank(receiptKey)) {
            saveRows(source, table, rows, account, batchSize);
            return true;
        }
        Row completedReceipt = Db.selectOneByMap(
                WRITE_RECEIPT_TABLE,
                Collections.singletonMap("idempotency_key", receiptKey));
        if (completedReceipt != null) {
            Object existingHash = completedReceipt.get("payload_hash");
            if (!Objects.equals(payloadHash, existingHash)) {
                throw new BusinessException("相同幂等键对应的数据内容不一致");
            }
            return false;
        }
        List<DatacenterTableField> fields = table.getFields();
        if (CollectionUtils.isEmpty(fields)) {
            throw new BusinessException("数据集字段为空，无法写入");
        }
        String actualTable = resolveTableName(table);
        int effectiveBatchSize = Math.max(1, batchSize);
        for (int offset = 0; offset < rows.size(); offset += effectiveBatchSize) {
            int end = Math.min(rows.size(), offset + effectiveBatchSize);
            List<PendingInternalRow> batch = new ArrayList<>(end - offset);
            for (int index = offset; index < end; index++) {
                batch.add(new PendingInternalRow(
                        WriteReceiptSupport.childKey(receiptKey, index),
                        rows.get(index),
                        buildRowMutation(
                                fields,
                                rows.get(index),
                                account)));
            }
            try {
                Db.txWithResult(() -> {
                    insertReceiptsBatch(batch, payloadHash);
                    executePendingMutations(actualTable, batch);
                    return true;
                });
            } catch (RuntimeException batchError) {
                /*
                 * 正常路径每批一个事务；批失败后才逐行回放，继续保持旧实现的
                 * 部分成功顺序边界，并利用子回执跳过已提交行。
                 */
                replayInternalRows(
                        actualTable,
                        fields,
                        batch,
                        account,
                        payloadHash);
            }
        }
        return Db.txWithResult(() -> {
            Row receipt = new Row();
            receipt.put("idempotency_key", receiptKey);
            receipt.put("payload_hash", payloadHash);
            receipt.put("created", new Date());
            try {
                Db.insert(WRITE_RECEIPT_TABLE, receipt);
            } catch (DuplicateKeyException duplicate) {
                Row existing = Db.selectOneByMap(
                        WRITE_RECEIPT_TABLE,
                        Collections.singletonMap("idempotency_key", receiptKey));
                Object existingHash = existing == null ? null : existing.get("payload_hash");
                if (!Objects.equals(payloadHash, existingHash)) {
                    throw new BusinessException("相同幂等键对应的数据内容不一致");
                }
                return false;
            }
            return true;
        });
    }

    /**
     * 在当前 MyBatis-Flex 事务中写入单行动态表数据。
     *
     * @param actualTable 实际表名
     * @param fields 可写字段
     * @param data 原始行
     * @param account 当前账号
     */
    private void saveRowInCurrentTransaction(
            String actualTable,
            List<DatacenterTableField> fields,
            JSONObject data,
            LoginAccount account) {
        RowMutation mutation = buildRowMutation(fields, data, account);
        if (mutation.insert) {
            Db.insert(actualTable, mutation.row);
        } else {
            Db.updateById(actualTable, mutation.row);
        }
    }

    /**
     * 创建写入回执。
     *
     * @param receiptKey 回执键
     * @param payloadHash 负载摘要
     */
    private void insertReceipt(String receiptKey, String payloadHash) {
        Row receipt = new Row();
        receipt.put("idempotency_key", receiptKey);
        receipt.put("payload_hash", payloadHash);
        receipt.put("created", new Date());
        Db.insert(WRITE_RECEIPT_TABLE, receipt);
    }

    /**
     * 在当前事务中批量创建子回执。
     *
     * @param rows 本批待写行
     * @param payloadHash 负载摘要
     */
    private void insertReceiptsBatch(
            List<PendingInternalRow> rows,
            String payloadHash) {
        Date created = new Date();
        List<Row> receipts = new ArrayList<>(rows.size());
        for (PendingInternalRow pendingRow : rows) {
            Row receipt = new Row();
            receipt.put(
                    "idempotency_key",
                    pendingRow.receiptKey());
            receipt.put("payload_hash", payloadHash);
            receipt.put("created", created);
            receipts.add(receipt);
        }
        Db.insertBatch(
                WRITE_RECEIPT_TABLE,
                receipts,
                receipts.size());
    }

    /**
     * 按原始顺序合并相邻同类型写入，减少正常路径 SQL 往返。
     *
     * @param actualTable 实际表名
     * @param rows 本批待写行
     */
    private void executePendingMutations(
            String actualTable,
            List<PendingInternalRow> rows) {
        List<Row> batch = new ArrayList<>(rows.size());
        Boolean insertBatch = null;
        for (PendingInternalRow pendingRow : rows) {
            RowMutation mutation = pendingRow.mutation();
            if (insertBatch != null
                    && insertBatch != mutation.insert) {
                executeRowBatch(
                        actualTable,
                        batch,
                        insertBatch);
                batch.clear();
            }
            insertBatch = mutation.insert;
            batch.add(mutation.row);
        }
        if (!batch.isEmpty()) {
            executeRowBatch(
                    actualTable,
                    batch,
                    Boolean.TRUE.equals(insertBatch));
        }
    }

    /**
     * 批失败后逐行回放，并校验重复回执的负载摘要。
     *
     * @param actualTable 实际表名
     * @param fields 可写字段
     * @param rows 本批数据
     * @param account 当前账号
     * @param payloadHash 负载摘要
     */
    private void replayInternalRows(
            String actualTable,
            List<DatacenterTableField> fields,
            List<PendingInternalRow> rows,
            LoginAccount account,
            String payloadHash) {
        for (PendingInternalRow pendingRow : rows) {
            Db.txWithResult(() -> {
                try {
                    insertReceipt(pendingRow.receiptKey(), payloadHash);
                } catch (DuplicateKeyException duplicate) {
                    Row existing = Db.selectOneByMap(
                            WRITE_RECEIPT_TABLE,
                            Collections.singletonMap(
                                    "idempotency_key",
                                    pendingRow.receiptKey()));
                    Object existingHash = existing == null
                            ? null
                            : existing.get("payload_hash");
                    if (!Objects.equals(payloadHash, existingHash)) {
                        throw new BusinessException(
                                "相同幂等键对应的数据内容不一致");
                    }
                    return false;
                }
                saveRowInCurrentTransaction(
                        actualTable,
                        fields,
                        pendingRow.data(),
                        account);
                return true;
            });
        }
    }

    /**
     * 内部动态表待写行。
     *
     * @param receiptKey 子回执键
     * @param data 原始行
     * @param mutation 已构建的写入对象
     */
    private record PendingInternalRow(
            String receiptKey,
            JSONObject data,
            RowMutation mutation) {
    }

    /**
     * 构建动态表单行写入对象。
     *
     * @param fields 数据表字段
     * @param data 输入数据
     * @param account 当前操作账号
     * @return 行数据与写入类型
     */
    private RowMutation buildRowMutation(
            List<DatacenterTableField> fields, JSONObject data, LoginAccount account) {
        Object id = data.get("id");
        if (id == null) {
            Date now = new Date();
            Row row = Row.ofKey(RowKey.SNOW_FLAKE_ID);
            row.put("dept_id", account.getDeptId());
            row.put("tenant_id", account.getTenantId());
            row.put("created", now);
            row.put("created_by", account.getId());
            row.put("modified", now);
            row.put("modified_by", account.getId());
            row.put("remark", data.get("remark"));
            for (DatacenterTableField field : fields) {
                row.put(field.getFieldName(), data.get(field.getFieldName()));
            }
            return new RowMutation(true, row);
        }
        Row row = Row.ofKey("id", id);
        row.put("modified", new Date());
        row.put("modified_by", account.getId());
        for (DatacenterTableField field : fields) {
            row.put(field.getFieldName(), data.get(field.getFieldName()));
        }
        return new RowMutation(false, row);
    }

    /**
     * 执行动态表同类型批次。
     *
     * @param actualTable 实际表名
     * @param rows 行数据
     * @param insert 是否为新增批次
     */
    private void executeRowBatch(String actualTable, List<Row> rows, boolean insert) {
        if (insert) {
            Db.insertBatch(actualTable, rows, rows.size());
        } else {
            Db.updateBatchById(actualTable, rows);
        }
    }

    /**
     * 动态表单行写入描述。
     */
    private static final class RowMutation {

        private final boolean insert;
        private final Row row;

        /**
         * 创建动态表行写入描述。
         *
         * @param insert 是否新增
         * @param row 行数据
         */
        private RowMutation(boolean insert, Row row) {
            this.insert = insert;
            this.row = row;
        }
    }

    @Override
    public void deleteRow(DatacenterSource source, DatacenterTable table, BigInteger id, LoginAccount account) {
        Db.deleteById(resolveTableName(table), Row.ofKey("id", id));
    }

    protected String resolveTableName(DatacenterTable table) {
        return StrUtil.blankToDefault(table.getMaterializedTable(), table.getActualTable());
    }

    /**
     * 统一内部表查询结果的字段名与值类型。
     *
     * <p>MySQL 驱动在部分查询路径会返回大写列标签，而数据集元数据中的物理字段名
     * 始终为小写。这里在序列化前收敛为小写，确保预览、SQL 消费和导出按同一字段名取值。</p>
     *
     * @param records 待规范化的数据行
     */
    static void normalizeRows(List<Row> records) {
        for (Row record : records) {
            Map<String, Object> converted = new LinkedHashMap<>();
            for (Map.Entry<String, Object> entry : record.entrySet()) {
                converted.put(
                        entry.getKey().toLowerCase(Locale.ROOT),
                        normalizeValue(entry.getValue()));
            }
            record.clear();
            record.putAll(converted);
        }
    }

    /**
     * 统一内部查询的数值 JSON 表现。
     *
     * @param value JDBC 原始值
     * @return 兼容既有查询接口的值
     */
    private static Object normalizeValue(Object value) {
        if (value instanceof BigInteger
                || value instanceof BigDecimal
                || value instanceof Long) {
            return value.toString();
        }
        return value;
    }
}
