package cn.fateverse.query.submeter.connection;

import cn.fateverse.query.submeter.executor.ForceExecuteTemplate;
import cn.fateverse.query.submeter.executor.ForceExecuteCallback;
import lombok.Getter;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


@Getter
public abstract class AbstractConnectionAdapter extends AbstractUnsupportedOperationConnection{



    private boolean autoCommit = true;
    private boolean readOnly = true;
    private volatile boolean closed;


    private int transactionIsolation = TRANSACTION_READ_UNCOMMITTED;


    private final List<Connection> cachedConnections = new LinkedList<>();

    private final ForceExecuteTemplate<Connection> forceExecuteTemplate = new ForceExecuteTemplate<>();

    private final ForceExecuteTemplate<Map.Entry<String, Connection>> forceExecuteTemplateForClose = new ForceExecuteTemplate<>();

    public final Connection getConnection() throws SQLException {
        Connection connection = getDataSource().getConnection();
        cachedConnections.add(connection);
        return connection;
    }

    public final List<Connection> getConnections() throws SQLException {
        if (cachedConnections.size() > 0){
            return cachedConnections;
        }
        Connection connection = getDataSource().getConnection();
        cachedConnections.add(connection);
        return cachedConnections;
    }


    protected abstract DataSource getDataSource();



    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        this.autoCommit = autoCommit;
    }

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

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

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

    private void rollbackForLocalTransaction() throws SQLException {
        forceExecuteTemplate.execute(cachedConnections, new ForceExecuteCallback<Connection>() {

            @Override
            public void execute(final Connection connection) throws SQLException {
                connection.rollback();
            }
        });
    }

    private void commitForLocalTransaction() throws SQLException {
        forceExecuteTemplate.execute(cachedConnections, new ForceExecuteCallback<Connection>() {
            @Override
            public void execute(final Connection connection) throws SQLException {
                connection.commit();
            }
        });
    }

    @Override
    public void close() throws SQLException {
        closed = true;
        try {
            forceExecuteTemplate.execute(cachedConnections , new ForceExecuteCallback<Connection>() {
                @Override
                public void execute(final Connection connection) throws SQLException {
                    connection.close();
                }
            });
        } finally {
            cachedConnections.clear();
        }
    }

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

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
        this.readOnly = readOnly;
    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return readOnly;
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        transactionIsolation = level;
//        recordMethodInvocation(Connection.class, "setTransactionIsolation", new Class[]{int.class}, new Object[]{level});
        forceExecuteTemplate.execute(cachedConnections, connection -> connection.setTransactionIsolation(level));
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        if (cachedConnections.isEmpty()) {
            return transactionIsolation;
        }
        return cachedConnections.iterator().next().getTransactionIsolation();
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return null;
    }

    @Override
    public void clearWarnings() throws SQLException {

    }


    @Override
    public void setHoldability(int holdability) throws SQLException {

    }

    @Override
    public int getHoldability() throws SQLException {
        return ResultSet.CLOSE_CURSORS_AT_COMMIT;
    }

}
