package com.ctrip.platform.dal.dao.datasource;

import com.ctrip.platform.dal.dao.helper.DalElementFactory;
import com.ctrip.platform.dal.dao.helper.LoggerHelper;
import com.ctrip.platform.dal.dao.helper.MySqlConnectionHelper;
import com.ctrip.platform.dal.dao.log.DalLogTypes;
import com.ctrip.platform.dal.dao.log.ILogger;
import com.mysql.jdbc.MySQLConnection;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.apache.tomcat.jdbc.pool.PooledConnection;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class DataSourceValidator implements ValidatorProxy {
    private static ILogger LOGGER = DalElementFactory.DEFAULT.getILogger();
    private static final int DEFAULT_VALIDATE_TIMEOUT_IN_SECONDS = 5;
    private static final String CONNECTION_VALIDATE_CONNECTION_FORMAT = "Connection::validateConnection:%s";
    private static final String IS_VALID_RETURN_INFO = "isValid() returned false.";
    private String IS_VALID_FORMAT = "isValid: %s";
    private String VALIDATE_ERROR_FORMAT = "Connection validation error:%s";

    private PoolProperties poolProperties;

    @Override
    public boolean validate(Connection connection, int validateAction) {
        long startTime = System.currentTimeMillis();
        boolean isValid = false;
        String connectionUrl = "";
        String transactionName = "";

        try {
            connectionUrl = LoggerHelper.getSimplifiedDBUrl(connection.getMetaData().getURL());
            transactionName = String.format(CONNECTION_VALIDATE_CONNECTION_FORMAT, connectionUrl);
            isValid = validateConnection(connection, validateAction);
            LOGGER.logTransaction(DalLogTypes.DAL_DATASOURCE, transactionName, String.format(IS_VALID_FORMAT, isValid), startTime);

            if (!isValid) {
                LOGGER.warn(IS_VALID_RETURN_INFO);
            }
        } catch (Throwable e) {
            StringBuilder sb = new StringBuilder();
            if (!isValid) {
                sb.append(IS_VALID_RETURN_INFO);
                sb.append(" "); // space
            }
            sb.append(String.format(VALIDATE_ERROR_FORMAT, e.getMessage()));
            LOGGER.warn(sb.toString());
            LOGGER.logTransaction(DalLogTypes.DAL_DATASOURCE, transactionName, sb.toString(), e, startTime);
        }

        return isValid;
    }

    private boolean validateConnection(Connection connection, int validateAction) throws SQLException {
        QueryParameter queryParameter = getQueryParameter(validateAction);
        return isValid(connection, queryParameter);
    }

    private QueryParameter getQueryParameter(int validateAction) {
        QueryParameter parameter = null;
        if (validateAction != PooledConnection.VALIDATE_INIT)
            return parameter;

        PoolProperties poolProperties = getPoolProperties();
        if (poolProperties == null)
            return parameter;

        String query = poolProperties.getInitSQL();
        int validationQueryTimeout = poolProperties.getValidationQueryTimeout();
        if (validationQueryTimeout <= 0) {
            validationQueryTimeout = DEFAULT_VALIDATE_TIMEOUT_IN_SECONDS;
        }

        parameter = new QueryParameter();
        parameter.setQuery(query);
        parameter.setValidationQueryTimeout(validationQueryTimeout);
        return parameter;
    }

    private boolean isValid(Connection connection, QueryParameter parameter) throws SQLException {
        boolean isValid;
        String query = null;
        if (parameter != null) {
            query = parameter.getQuery();
        }

        if (query == null) {
            isValid = connectionIsValid(connection);
        } else {
            isValid = executeInitSQL(connection, parameter);
        }

        return isValid;
    }

    private boolean connectionIsValid(Connection connection) throws SQLException {
        boolean isValid;

        if (connection instanceof MySQLConnection) {
            MySQLConnection mySqlConnection = (MySQLConnection) connection;
            isValid = MySqlConnectionHelper.isValid(mySqlConnection, DEFAULT_VALIDATE_TIMEOUT_IN_SECONDS);
        } else {
            isValid = connection.isValid(DEFAULT_VALIDATE_TIMEOUT_IN_SECONDS);
        }

        return isValid;
    }

    private boolean executeInitSQL(Connection connection, QueryParameter parameter) throws SQLException {
        boolean isValid = false;
        String query = parameter.getQuery();
        int validationQueryTimeout = parameter.getValidationQueryTimeout();

        Statement stmt = null;
        try {
            stmt = connection.createStatement();
            stmt.setQueryTimeout(validationQueryTimeout);
            stmt.execute(query);
            isValid = true;
        } finally {
            if (stmt != null)
                try {
                    stmt.close();
                } catch (Exception ignore2) {
                    /* NOOP */}
        }

        return isValid;
    }

    private class QueryParameter {
        private String query = null;
        private int validationQueryTimeout = -1;

        public String getQuery() {
            return query;
        }

        public void setQuery(String query) {
            this.query = query;
        }

        public int getValidationQueryTimeout() {
            return validationQueryTimeout;
        }

        public void setValidationQueryTimeout(int validationQueryTimeout) {
            this.validationQueryTimeout = validationQueryTimeout;
        }
    }

    private PoolProperties getPoolProperties() {
        return poolProperties;
    }

    @Override
    public void setPoolProperties(PoolProperties poolProperties) {
        this.poolProperties = poolProperties;
    }

    public static void setILogger(ILogger logger) {
        DataSourceValidator.LOGGER = logger;
    }

}