package org.openjproxy.grpc.server.statement;

import com.openjproxy.grpc.StatementRequest;
import org.apache.commons.lang3.StringUtils;
import org.openjproxy.constants.CommonConstants;
import org.openjproxy.grpc.ProtoConverter;
import org.openjproxy.grpc.dto.Parameter;
import org.openjproxy.grpc.server.ConnectionSessionDTO;
import org.openjproxy.grpc.server.SessionManager;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;

import static org.openjproxy.grpc.server.Constants.EMPTY_MAP;

/**
 * Factory class for creating different types of SQL statements.
 * Extracted from StatementServiceImpl to improve modularity.
 */
public class StatementFactory {

    /**
     * Creates a Statement based on the request properties.
     *
     * @param sessionManager The session manager
     * @param connection    The database connection
     * @param request       The statement request
     * @return Created Statement
     * @throws SQLException if statement creation fails
     */
    public static Statement createStatement(SessionManager sessionManager, Connection connection, 
                                          StatementRequest request) throws SQLException {
        try {
            if (StringUtils.isNotEmpty(request.getStatementUUID())) {
                return sessionManager.getStatement(request.getSession(), request.getStatementUUID());
            }
            if (request.getPropertiesList().isEmpty()) {
                return connection.createStatement();
            }
            Map<String, Object> properties = ProtoConverter.propertiesFromProto(request.getPropertiesList());

            if (properties.isEmpty() ||
                    (properties.size() == 1 && properties.get(CommonConstants.PREPARED_STATEMENT_SQL_KEY) != null)) {
                return connection.createStatement();
            }
            if (properties.size() == 2) {
                return connection.createStatement(
                        (Integer) properties.get(CommonConstants.STATEMENT_RESULT_SET_TYPE_KEY),
                        (Integer) properties.get(CommonConstants.STATEMENT_RESULT_SET_CONCURRENCY_KEY));
            }
            if (properties.size() == 3) {
                return connection.createStatement(
                        (Integer) properties.get(CommonConstants.STATEMENT_RESULT_SET_TYPE_KEY),
                        (Integer) properties.get(CommonConstants.STATEMENT_RESULT_SET_CONCURRENCY_KEY),
                        (Integer) properties.get(CommonConstants.STATEMENT_RESULT_SET_HOLDABILITY_KEY));
            }
            throw new SQLException("Incorrect number of properties for creating a new statement.");
        } catch (RuntimeException re) {
            throw new SQLException("Unable to create statement: " + re.getMessage(), re);
        }
    }

    /**
     * Creates a PreparedStatement with the given SQL and parameters.
     *
     * @param sessionManager The session manager
     * @param dto           The connection session DTO
     * @param sql           The SQL statement
     * @param params        The parameters
     * @param request       The statement request
     * @return Created PreparedStatement
     * @throws SQLException if prepared statement creation fails
     */
    public static PreparedStatement createPreparedStatement(SessionManager sessionManager, 
                                                          ConnectionSessionDTO dto, String sql, 
                                                          List<Parameter> params, StatementRequest request)
            throws SQLException {

        PreparedStatement ps = null;
        Map<String, Object> properties = EMPTY_MAP;
        if (!request.getPropertiesList().isEmpty()) {
            properties = ProtoConverter.propertiesFromProto(request.getPropertiesList());
        }
        if (properties.isEmpty()) {
            ps = dto.getConnection().prepareStatement(sql);
        }
        if (properties.size() == 1) {
            int[] columnIndexes = (int[]) properties.get(CommonConstants.STATEMENT_COLUMN_INDEXES_KEY);
            String[] columnNames = (String[]) properties.get(CommonConstants.STATEMENT_COLUMN_INDEXES_KEY);
            Boolean isAddBatch = (Boolean) properties.get(CommonConstants.PREPARED_STATEMENT_ADD_BATCH_FLAG);
            Integer autoGeneratedKeys = (Integer) properties.get(CommonConstants.STATEMENT_AUTO_GENERATED_KEYS_KEY);
            if (columnIndexes != null) {
                ps = dto.getConnection().prepareStatement(sql, columnIndexes);
            } else if (columnNames != null) {
                ps = dto.getConnection().prepareStatement(sql, columnNames);
            } else if (isAddBatch != null && isAddBatch) {
                ps = dto.getConnection().prepareStatement(sql);
            } else if (autoGeneratedKeys != null) {
                ps = dto.getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            }
        }
        Integer resultSetType = (Integer) properties.get(CommonConstants.STATEMENT_RESULT_SET_TYPE_KEY);
        Integer resultSetConcurrency = (Integer) properties.get(CommonConstants.STATEMENT_RESULT_SET_CONCURRENCY_KEY);
        Integer resultSetHoldability = (Integer) properties.get(CommonConstants.STATEMENT_RESULT_SET_HOLDABILITY_KEY);

        if (resultSetType != null && resultSetConcurrency != null && resultSetHoldability == null) {
            ps = dto.getConnection().prepareStatement(sql, resultSetType, resultSetConcurrency);
        }
        if (resultSetType != null && resultSetConcurrency != null && resultSetHoldability != null) {
            ps = dto.getConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
        }
        if (ps == null) {
            throw new SQLException("Incorrect number of properties for creating a new prepared statement.");
        }

        ParameterHandler.addParametersPreparedStatement(sessionManager, dto.getSession(), ps, params);
        return ps;
    }
}