package com.seeyon.apps.src_ydctdatasync.datasource;

import com.seeyon.apps.src_ydctdatasync.config.DataSourceConfig;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class DataSource {
    private DataSourceConfig config;
    private final int maximumPoolSize = 5;
    private final int minimumIdle = 3;
    // 连接队列
    private final BlockingQueue<Connection> idleConnections = new LinkedBlockingQueue<>();
    private final Queue<Connection> activeConnections = new ConcurrentLinkedQueue<>();

    // 构造方法
    public DataSource() {

    }

    public DataSourceConfig getConfig() {
        return config;
    }

    public void setConfig(DataSourceConfig config) {
        this.config = config;
    }

    // 创建一个新的数据库连接
    private Connection createNewConnection() {
        try {
            Class.forName(config.getDriver());
            Connection realConnection = DriverManager.getConnection(config.getUrl(),config.getUsername(), config.getPassword());
            return (Connection) Proxy.newProxyInstance(
                    realConnection.getClass().getClassLoader(),
                    new Class[]{Connection.class},
                    new ConnectionProxyHandler(realConnection, this)
            );
        } catch (SQLException e) {
            throw new RuntimeException("Failed to create a new connection", e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }


    // 从连接池获取连接
    public Connection getConnection() throws SQLException {
        Connection connection = idleConnections.poll();
        if (connection == null) {
            synchronized (this) {
                if (activeConnections.size() + idleConnections.size() < maximumPoolSize) {
                    connection = createNewConnection();
                } else {
                    try {
                        // 等待空闲连接释放
                        connection = idleConnections.poll(10, TimeUnit.SECONDS);
                        if (connection == null) {
                            throw new SQLException("No available connection in the pool");
                        }
                    } catch (InterruptedException e) {
                        throw new SQLException("Interrupted while waiting for a connection", e);
                    }
                }
            }
        }

        if (connection != null) {
            activeConnections.offer(connection);
        }

        return connection;
    }

    // 归还连接到连接池
    public void releaseConnection(Connection connection) {
        if (connection != null) {
            activeConnections.remove(connection);
            idleConnections.offer(connection);
        }
    }

    // 销毁连接池中的所有连接
    public void close() {
        for (Connection connection : idleConnections) {
            closeConnection(connection);
        }
        for (Connection connection : activeConnections) {
            closeConnection(connection);
        }
    }

    private void closeConnection(Connection connection) {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 代理处理器，用于拦截 close 方法
    private class ConnectionProxyHandler implements InvocationHandler {
        private final Connection realConnection;
        private final DataSource pool;

        public ConnectionProxyHandler(Connection realConnection, DataSource pool) {
            this.realConnection = realConnection;
            this.pool = pool;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("close".equals(method.getName())) {
                pool.releaseConnection((Connection) proxy);
                return null;
            }
            return method.invoke(realConnection, args);
        }
    }
}