package net.tccn.base.dbq.jdbc.api;

import net.tccn.base.CfgException;
import net.tccn.base.Kv;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by liangxianyou at 2019/3/12 14:20.
 */
public class DbSourceMysql implements DbSource {

    private static ConcurrentHashMap<String, LinkedBlockingQueue<Connection>> conns = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String, AtomicInteger> counter = new ConcurrentHashMap<>();

    private String accountKey;
    private DbAccount dbAccount;
    private String catalog;

    private static boolean ping;

    static {
        // MYSQL PING
        synchronized (DbSourceMysql.class) {
            if (!ping) {
                new Thread(() -> {
                    while (true) {
                        conns.forEach((k, vs) -> {
                            if (vs.isEmpty()) {
                                return;
                            }

                            try {
                                for (int i = 0; i < counter.get(k).get(); i++) {
                                    Connection conn = vs.poll();
                                    if (conn == null) {
                                        return;
                                    }
                                    try {
                                        conn.prepareStatement("SELECT 1").executeQuery();
                                        vs.put(conn);
                                    } catch (SQLException e) {
                                        counter.get(k).decrementAndGet(); // 连接总数减去1
                                        e.printStackTrace();
                                    }
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        });

                        try {
                            Thread.sleep(1000 * 60);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }).start();
                ping = true;
            }
        }
    }

    public DbSourceMysql() {

    }

    public void setDbAccount(DbAccount dbAccount) {
        this.dbAccount = dbAccount;
        this.accountKey = dbAccount.accountKey();
    }

    @Override
    public void setCatalog(String catalog) {
        this.catalog = catalog;
    }

    public DbSourceMysql(DbAccount dbAccount) {
        this.dbAccount = dbAccount;
        this.accountKey = dbAccount.accountKey();
    }

    public DbSourceMysql(DbAccount dbAccount, String catalog) {
        this.dbAccount = dbAccount;
        this.catalog = catalog;
        this.accountKey = dbAccount.accountKey();
    }

    @Override
    public String getType() {
        return "mysql";
    }

    @Override
    public <T> List<T> queryList(String sql, Class<T> type) {
        Connection connection = connection();
        try (
                PreparedStatement ps = connection.prepareStatement(sql);
                ResultSet rs = ps.executeQuery()
        ) {
            List list = new ArrayList();
            ResultSetMetaData metaData = rs.getMetaData();
            int count = metaData.getColumnCount();
            while (rs.next()) {
                Kv row = Kv.of();
                for (int i = 1; i <= count; i++) {
                    String columnTypeName = metaData.getColumnTypeName(i);
                    //String columnName = metaData.getColumnName(i);
                    String columnLabel = metaData.getColumnLabel(i);
                    row.put(columnLabel, null);

                    if (rs.getObject(i) != null) {
                        switch (columnTypeName) {
                            case "DATETIME", "TIMESTAMP" -> {
                                // row.put(columnLabel, rs.getTimestamp(i).getTime());
                                row.put(columnLabel, rs.getTimestamp(i));
                            }
                            case "DATE" -> {
                                row.put(columnLabel, rs.getDate(i));
                            }
                            default -> {
                                row.put(columnLabel, rs.getObject(i));
                            }
                        }
                    }
                }
                list.add(Kv.toAs(row, type));
            }

            return list;
        } catch (SQLException e) {
            System.out.println("sql :" + sql);
            e.printStackTrace();
            return null;
        } finally {
            release(connection);
        }
    }

    @Override
    public <T> T find(String sql, Class<T> type) {
        List<T> list = queryList(sql, type);
        return list.size() > 0 ? list.get(0) : null;
    }

    @Override
    public <T> T findColumn(String sql, Class<T> type) {
        Connection connection = connection();
        try (
                PreparedStatement ps = connection.prepareStatement(sql);
                ResultSet rs = ps.executeQuery()
        ) {
            Object v = null;
            while (rs.next()) {
                ResultSetMetaData metaData = rs.getMetaData();
                int count = metaData.getColumnCount();

                for (int i = 1; i <= count; i++) {
                    String columnTypeName = metaData.getColumnTypeName(i);
                    if (rs.getObject(i) != null) {
                        switch (columnTypeName) {
                            case "DATETIME":
                            case "TIMESTAMP":
                            case "DATE":
                                v = rs.getTimestamp(i).getTime();
                                break;
                            default:
                                v = rs.getObject(i);
                        }
                    }
                }
            }

            return Kv.toAs(v, type);
        } catch (SQLException e) {
            System.out.println("sql execute error: " + sql);
            e.printStackTrace();
            return null;
        } finally {
            release(connection);
        }
    }

    @Override
    public void createTable(String sql) {
        new RuntimeException("DbSourceMysql.createTable NOT SUPPORT right now"); // todo:
    }

    @Override
    public void dropTable(String tableName) {
        new RuntimeException("[DbSourceMysql.dropTable] NOT SUPPORT right now"); // todo:
    }

    @Override
    public boolean exetute(String sql) {
        Connection connection = connection();
        try (
                PreparedStatement ps = connection.prepareStatement(sql);
        ) {
            return ps.execute();
            //ps.executeUpdate();
        } catch (SQLException e) {
            throw new CfgException("SQL 执行失败：", sql);
        } finally {
            release(connection);
        }
    }

    @Override
    public int exetuteUpdate(String sql) {
        Connection connection = connection();
        try (
                PreparedStatement ps = connection.prepareStatement(sql)
        ) {
            return ps.executeUpdate();
        } catch (SQLException e) {
            throw new CfgException("SQL 执行失败：", sql);
        } finally {
            release(connection);
        }
    }

    //fixme: lxy 处理连接超过8小时失效问题
    private Connection connection() {
        Connection connection = null;
        try {
            connection = connection(0);
            if (catalog != null && !catalog.isEmpty()) {
                connection.setCatalog(catalog); //还回连接的时候是否需要重置catalog？ 后续观察
            }
        } catch (InterruptedException | SQLException e) {
            new IllegalArgumentException("获取连接失败", e);
        }
        return connection;
    }

    private synchronized Connection connection(int n) throws InterruptedException, SQLException {
        LinkedBlockingQueue<Connection> queue = conns.get(accountKey);
        if (queue == null) {
            queue = new LinkedBlockingQueue<>(15);
            conns.put(accountKey, queue);
        }
        AtomicInteger num = counter.get(accountKey);
        if (num == null) {
            num = new AtomicInteger(0);
        }

        Connection conn = null;
        if (queue.isEmpty() && num.get() < 15) { // 创建总连接数小于15，且暂无可用连接
            String url = dbAccount.getUrl();
            String user = dbAccount.getUser();
            String pwd = dbAccount.getPwd();

            conn = DriverManager.getConnection(url, user, pwd);
            int x = num.incrementAndGet();
            counter.put(accountKey, num);
            System.out.printf("创建新的连接[%s]:%s\n", x, url + "@" + user);

        } else {
            conn = queue.take();
            if (!conn.isValid(5)) {
                conn = connection(++n);
            }
        }
        //conns.put(accountKey, queue);
        return conn;
    }

    private void release(Connection connection) {
        LinkedBlockingQueue<Connection> queue = conns.getOrDefault(accountKey, new LinkedBlockingQueue<>(15));
        try {
            if (connection != null) {
                queue.put(connection);
                conns.put(accountKey, queue);
                //System.out.println("还回连接：" + connection);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
