package com.tashow.cloud.canal.service;

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

@Service
public class CanalSyncService {

    private static final Logger log = LoggerFactory.getLogger(CanalSyncService.class);

    private static final Queue<SqlTask> SQL_QUEUE = new ConcurrentLinkedQueue<>();

    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private SqlExecutorService sqlExecutorService;

    @PostConstruct
    public void start() {
        new Thread(this::runCanalClient).start();
    }

    private void runCanalClient() {
        CanalConnector connector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("43.139.42.137", 11111),
                "example",
                "",
                ""
        );
        int batchSize = 1000;

        try {
            connector.connect();
            connector.subscribe("tashow-platform\\..*");
            connector.rollback();

            while (true) {
                Message message = connector.getWithoutAck(batchSize);
                log.info("Received message id: {}, entries size: {}", message.getId(), message.getEntries().size());
                long batchId = message.getId();
                int size = message.getEntries().size();

                if (batchId == -1 || size == 0) {
                    Thread.sleep(1000);
                    continue;
                }

                dataHandle(message.getEntries());
                connector.ack(batchId);

                if (!SQL_QUEUE.isEmpty()) {
                    executeQueueSql();
                }
            }
        } catch (Exception e) {
            log.error("Canal client error occurred.", e);
        } finally {
            connector.disconnect();
        }
    }

    private void dataHandle(List<Entry> entries) {
        for (Entry entry : entries) {
            if (entry.getEntryType() != EntryType.ROWDATA) continue;

            try {
                RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
                EventType eventType = rowChange.getEventType();
                String schemaName = entry.getHeader().getSchemaName();
                String tableName = entry.getHeader().getTableName();

                log.info("schema: {}, table: {}, type: {}", schemaName, tableName, eventType);

                if (eventType == EventType.DELETE) {
                    saveDeleteSql(entry);
                } else if (eventType == EventType.UPDATE) {
                    saveUpdateSql(entry);
                } else if (eventType == EventType.INSERT) {
                    saveInsertSql(entry);
                }

            } catch (Exception e) {
                log.error("Error handling entry: {}", entry.toString(), e);
            }
        }
    }

    private void saveInsertSql(Entry entry) throws Exception {
        RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
        String tableName = entry.getHeader().getTableName();

        for (RowData rowData : rowChange.getRowDatasList()) {
            List<Column> columns = rowData.getAfterColumnsList();

            List<String> columnNames = new ArrayList<>();
            List<Object> values = new ArrayList<>();

            for (Column col : columns) {
                columnNames.add(col.getName());
                values.add(col.getValue());
            }

            String sql = "INSERT INTO " + tableName + " (" +
                    String.join(",", columnNames) + ") VALUES (";

            StringBuilder placeholders = new StringBuilder();
            for (int i = 0; i < values.size(); i++) {
                placeholders.append("?,");
            }
            if (placeholders.length() > 0) placeholders.deleteCharAt(placeholders.length() - 1);
            sql += placeholders + ")";

            SQL_QUEUE.add(new SqlTask(sql, values.toArray()));
        }
    }

    private void saveUpdateSql(Entry entry) throws Exception {
        RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
        String tableName = entry.getHeader().getTableName();

        for (RowData rowData : rowChange.getRowDatasList()) {
            List<Column> newColumns = rowData.getAfterColumnsList();
            List<Column> oldColumns = rowData.getBeforeColumnsList();

            List<String> updateColumns = new ArrayList<>();
            List<Object> params = new ArrayList<>();

            for (Column col : newColumns) {
                updateColumns.add(col.getName() + "=?");
                params.add(col.getValue());
            }

            Optional<Column> primaryKeyOpt = oldColumns.stream().filter(Column::getIsKey).findFirst();
            Column primaryKey = primaryKeyOpt.orElseThrow(() -> new RuntimeException("未找到主键"));

            params.add(primaryKey.getValue());

            String sql = "UPDATE " + tableName + " SET " +
                    String.join(",", updateColumns) +
                    " WHERE " + primaryKey.getName() + "=?";

            SQL_QUEUE.add(new SqlTask(sql, params.toArray()));
        }
    }

    private void saveDeleteSql(Entry entry) throws Exception {
        RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
        String tableName = entry.getHeader().getTableName();

        for (RowData rowData : rowChange.getRowDatasList()) {
            List<Column> beforeColumns = rowData.getBeforeColumnsList();

            Optional<Column> primaryKeyOpt = beforeColumns.stream().filter(Column::getIsKey).findFirst();
            Column primaryKey = primaryKeyOpt.orElseThrow(() -> new RuntimeException("未找到主键"));

            String sql = "DELETE FROM " + tableName + " WHERE " + primaryKey.getName() + "=?";
            SQL_QUEUE.add(new SqlTask(sql, primaryKey.getValue()));
        }
    }

    private void executeQueueSql() {
        List<SqlTask> tasks = new ArrayList<>();
        SqlTask task;
        while ((task = SQL_QUEUE.poll()) != null) {
            tasks.add(task);
        }
        if (!tasks.isEmpty()) {
            sqlExecutorService.executeBatch(tasks);
        }
    }
}