/**
 * Copyright 2021 Shulie Technology, Co.Ltd
 * Email: shulie@shulie.io
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.pamirs.attach.plugin.alibaba.druid.obj;

import com.alibaba.druid.pool.DruidConnectionHolder;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.druid.pool.DruidPooledPreparedStatement;
import com.alibaba.druid.proxy.jdbc.TransactionInfo;
import com.pamirs.attach.plugin.common.datasource.biz.BizCallableStatement;
import com.pamirs.attach.plugin.common.datasource.biz.BizPreparedStatement;
import com.pamirs.attach.plugin.common.datasource.biz.BizStatement;
import com.pamirs.attach.plugin.common.datasource.trace.CheckedTraceCallableStatement;
import com.pamirs.attach.plugin.common.datasource.trace.CheckedTracePreparedStatement;
import com.pamirs.attach.plugin.common.datasource.trace.CheckedTraceStatement;
import com.pamirs.attach.plugin.common.datasource.utils.ProxyFlag;
import com.pamirs.attach.plugin.dynamic.reflect.ReflectionUtils;
import com.pamirs.pradar.pressurement.datasource.util.DbType;
import com.pamirs.pradar.pressurement.datasource.util.SqlMetaData;

import javax.sql.ConnectionEventListener;
import javax.sql.StatementEventListener;
import java.sql.*;

/**
 * 业务连接
 *
 * @author xiaobin.zfb
 * @since 2020/8/13 9:12 下午
 */
public class BizConnection extends DruidPooledConnection implements Connection {
    private String url;
    private String username;
    private String dbType;
    private SqlMetaData sqlMetaData;
    private DruidPooledConnection target;

    public BizConnection(DruidPooledConnection connection, String url, String username, String dbType, SqlMetaData sqlMetaData) {
        super(connection.getConnectionHolder());
        this.target = connection;
        this.url = url;
        this.username = username;
        this.dbType = dbType;
        if (sqlMetaData == null) {
            DbType type = DbType.nameOf(dbType);
            if (type != null) {
                try {
                    this.sqlMetaData = type.sqlMetaData(url);
                } catch (Throwable e) {
                }
            }
        } else {
            this.sqlMetaData = sqlMetaData;
        }
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        try {
            ProxyFlag.enter();
            return new CheckedTracePreparedStatement(new BizPreparedStatement(target.prepareStatement(sql)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
        } finally {
            ProxyFlag.exit();
        }
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        try {
            ProxyFlag.enter();
            return new CheckedTracePreparedStatement(new BizPreparedStatement(target.prepareStatement(sql, resultSetType, resultSetConcurrency)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
        } finally {
            ProxyFlag.exit();
        }
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        try {
            ProxyFlag.enter();
            return new CheckedTracePreparedStatement(new BizPreparedStatement(target.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
        } finally {
            ProxyFlag.exit();
        }
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        try {
            ProxyFlag.enter();
            return new CheckedTracePreparedStatement(new BizPreparedStatement(target.prepareStatement(sql, columnIndexes)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
        } finally {
            ProxyFlag.exit();
        }
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        try {
            ProxyFlag.enter();
            return new CheckedTracePreparedStatement(new BizPreparedStatement(target.prepareStatement(sql, columnNames)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
        } finally {
            ProxyFlag.exit();
        }
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        try {
            ProxyFlag.enter();
            return new CheckedTracePreparedStatement(new BizPreparedStatement(target.prepareStatement(sql, autoGeneratedKeys)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
        } finally {
            ProxyFlag.exit();
        }
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return new CheckedTraceCallableStatement(new BizCallableStatement(target.prepareCall(sql)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return new CheckedTraceCallableStatement(new BizCallableStatement(target.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return new CheckedTraceCallableStatement(new BizCallableStatement(target.prepareCall(sql, resultSetType, resultSetConcurrency)), sql, this.url, this.username, this.dbType, false, sqlMetaData);
    }

    @Override
    public Statement createStatement() throws SQLException {
        return new CheckedTraceStatement(new BizStatement(target.createStatement()), this.url, this.username, this.dbType, false, sqlMetaData);
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return new CheckedTraceStatement(new BizStatement(target.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability)), this.url, this.username, this.dbType, false, sqlMetaData);
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return new CheckedTraceStatement(new BizStatement(target.createStatement(resultSetType, resultSetConcurrency)), this.url, this.username, this.dbType, false, sqlMetaData);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return target.isWrapperFor(iface);
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return target.unwrap(iface);
    }

    @Override
    public void commit() throws SQLException {
        target.commit();
    }

    @Override
    public void rollback() throws SQLException {
        target.rollback();
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        target.rollback(savepoint);
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return target.getAutoCommit();
    }

    @Override
    public void recycle() throws SQLException {
        target.recycle();
    }

    @Override
    public boolean isClosed() throws SQLException {
        return target.isClosed();
    }

    @Override
    public void close() throws SQLException {
        super.close();
        target.close();
    }

    @Override
    public Thread getOwnerThread() {
        return target.getOwnerThread();
    }

    @Override
    public StackTraceElement[] getConnectStackTrace() {
        return target.getConnectStackTrace();
    }

    @Override
    public void setConnectStackTrace(StackTraceElement[] connectStackTrace) {
        target.setConnectStackTrace(connectStackTrace);
    }

    @Override
    public long getConnectedTimeNano() {
        return target.getConnectedTimeNano();
    }

    @Override
    public void setConnectedTimeNano(long connectedTimeNano) {
        target.setConnectedTimeNano(connectedTimeNano);
    }

    @Override
    public void setConnectedTimeNano() {
        target.setConnectedTimeNano();
    }

    @Override
    public boolean isTraceEnable() {
        return target.isTraceEnable();
    }

    @Override
    public void setTraceEnable(boolean traceEnable) {
        target.setTraceEnable(traceEnable);
    }

    @Override
    public SQLException handleException(Throwable t) throws SQLException {
        return target.handleException(t);
    }

    @Override
    public boolean isOracle() {
        return target.isOracle();
    }

    @Override
    public void closePoolableStatement(DruidPooledPreparedStatement stmt) throws SQLException {
        target.closePoolableStatement(stmt);
    }

    @Override
    public DruidConnectionHolder getConnectionHolder() {
        return target.getConnectionHolder();
    }

    @Override
    public Connection getConnection() {
        return target.getConnection();
    }

    @Override
    public void disable() {
        target.disable();
    }

    @Override
    public void disable(Throwable error) {
        target.disable(error);
    }

    @Override
    public boolean isDisable() {
        return target.isDisable();
    }

    @Override
    public synchronized void syncClose() throws SQLException {
        target.syncClose();
    }

    @Override
    public void transactionRecord(String sql) throws SQLException {
        try {
            ReflectionUtils.invoke(target, "transactionRecord", sql);
        } catch (Exception e) {
            throw new SQLException(e);
        }
    }

    @Override
    public TransactionInfo getTransactionInfo() {
        return target.getTransactionInfo();
    }

    @Override
    public boolean isAbandonded() {
        return target.isAbandonded();
    }

    @Override
    public void addConnectionEventListener(ConnectionEventListener listener) {
        target.addConnectionEventListener(listener);
    }

    @Override
    public void removeConnectionEventListener(ConnectionEventListener listener) {
        target.removeConnectionEventListener(listener);
    }

    @Override
    public void addStatementEventListener(StatementEventListener listener) {
        target.addStatementEventListener(listener);
    }

    @Override
    public void removeStatementEventListener(StatementEventListener listener) {
        target.removeStatementEventListener(listener);
    }

    @Override
    public Throwable getDisableError() {
        return target.getDisableError();
    }

    @Override
    public void checkState() throws SQLException {
        target.checkState();
    }

    @Override
    public void abandond() {
        target.abandond();
    }
}
