package com.eactive.eai.rms.common.dbconsole.service;

import java.io.Reader;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.eactive.eai.rms.common.datasource.DataSourceType;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.dbconsole.dto.ColumnInfoDto;
import com.eactive.eai.rms.common.dbconsole.dto.DatasourceInfoDto;
import com.eactive.eai.rms.common.dbconsole.dto.SqlRequestDto;
import com.eactive.eai.rms.common.dbconsole.dto.SqlResultDto;
import com.eactive.eai.rms.common.dbconsole.dto.TableInfoDto;
import com.eactive.eai.rms.common.util.ContainerUtil;

/**
 * DB Console 서비스
 * - SQL 실행, 트랜잭션 관리 (Auto Commit OFF)
 * - 세션 타임아웃 관리 (30분)
 */
@Service
public class DbConsoleService {

    private static final Logger log = LoggerFactory.getLogger(DbConsoleService.class);

    /**
     * 세션별 커넥션 관리 (트랜잭션 유지용)
     */
    private final ConcurrentHashMap<String, Connection> sessionConnections = new ConcurrentHashMap<>();

    /**
     * 세션별 마지막 활동 시간 관리
     */
    private final ConcurrentHashMap<String, Long> sessionLastActivity = new ConcurrentHashMap<>();

    /**
     * 세션 타임아웃 (30분, 밀리초)
     */
    private static final long SESSION_TIMEOUT_MS = 30 * 60 * 1000;

    /**
     * 사용 가능한 데이터소스 목록 반환
     * - DataSourceTypeManager에서 등록된 데이터소스 조회
     */
    public List<DatasourceInfoDto> getAvailableDatasources() {
        List<DatasourceInfoDto> datasources = new ArrayList<>();

        // DataSourceTypeManager에서 데이터소스 목록 조회
        List<DataSourceType> types = DataSourceTypeManager.getDataSourceTypes();
        for (DataSourceType type : types) {
            datasources.add(DatasourceInfoDto.builder()
                .displayName(type.getText() + " (" + type.getSchema() + ")")
                .type("JNDI")
                .description(type.getName() + " Database")
                .jndiName(type.getJndiName())
                .schema(type.getSchema())
                .build());
        }

        // 직접 입력 옵션 (항상 추가)
        datasources.add(DatasourceInfoDto.builder()
            .displayName("직접 입력")
            .type("DIRECT")
            .description("JDBC URL을 직접 입력하여 연결")
            .driverClassName("oracle.jdbc.OracleDriver")
            .build());

        return datasources;
    }

    /**
     * 새 세션 생성
     */
    public String createSession() {
        return UUID.randomUUID().toString();
    }

    /**
     * SQL 실행
     */
    public SqlResultDto executeSql(SqlRequestDto request) {
        long startTime = System.currentTimeMillis();

        try {
            String action = request.getAction();
            if (action == null) {
                action = "EXECUTE";
            }

            switch (action.toUpperCase()) {
                case "COMMIT":
                    return commit(request.getSessionId());
                case "ROLLBACK":
                    return rollback(request.getSessionId());
                case "DISCONNECT":
                    return disconnect(request.getSessionId());
                default:
                    return executeQuery(request, startTime);
            }
        } catch (Exception e) {
            log.error("SQL 실행 오류", e);
            return SqlResultDto.error("SQL 실행 오류: " + e.getMessage());
        }
    }

    /**
     * 쿼리 실행 (여러 SQL문 지원 - 세미콜론 구분)
     */
    private SqlResultDto executeQuery(SqlRequestDto request, long startTime) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            conn = getOrCreateConnection(request);
            stmt = conn.createStatement();

            if (request.getMaxRows() != null && request.getMaxRows() > 0) {
                stmt.setMaxRows(request.getMaxRows());
            }

            String fullSql = request.getSql().trim();

            // 여러 SQL문 분리 (세미콜론 기준, 문자열 리터럴 내부는 제외)
            List<String> sqlStatements = splitSqlStatements(fullSql);

            // 단일 SQL문인 경우
            if (sqlStatements.size() == 1) {
                return executeSingleQuery(stmt, sqlStatements.get(0), request.getSessionId(), startTime);
            }

            // 여러 SQL문인 경우 순차 실행
            return executeMultipleQueries(stmt, sqlStatements, request.getSessionId(), startTime);

        } catch (SQLException e) {
            log.error("SQL 실행 오류: {}", e.getMessage());
            SqlResultDto result = SqlResultDto.error("SQL 오류: " + e.getMessage());
            result.setSessionId(request.getSessionId());
            result.setTransactionActive(isTransactionActive(request.getSessionId()));
            return result;
        } finally {
            closeQuietly(rs);
            closeQuietly(stmt);
            // Connection은 닫지 않음 (트랜잭션 유지)
        }
    }

    /**
     * SQL문 분리 (세미콜론 기준, 문자열 리터럴 내부 세미콜론은 무시)
     */
    private List<String> splitSqlStatements(String fullSql) {
        List<String> statements = new ArrayList<>();
        StringBuilder current = new StringBuilder();
        boolean inSingleQuote = false;
        boolean inDoubleQuote = false;

        for (int i = 0; i < fullSql.length(); i++) {
            char c = fullSql.charAt(i);

            // 문자열 리터럴 시작/종료 체크
            if (c == '\'' && !inDoubleQuote) {
                // 이스케이프된 작은따옴표 체크 ('')
                if (i + 1 < fullSql.length() && fullSql.charAt(i + 1) == '\'') {
                    current.append(c);
                    current.append(fullSql.charAt(i + 1));
                    i++;
                    continue;
                }
                inSingleQuote = !inSingleQuote;
            } else if (c == '"' && !inSingleQuote) {
                inDoubleQuote = !inDoubleQuote;
            }

            // 세미콜론이고 문자열 내부가 아닌 경우
            if (c == ';' && !inSingleQuote && !inDoubleQuote) {
                String sql = current.toString().trim();
                if (!sql.isEmpty()) {
                    statements.add(sql);
                }
                current = new StringBuilder();
            } else {
                current.append(c);
            }
        }

        // 마지막 SQL (세미콜론 없이 끝난 경우)
        String lastSql = current.toString().trim();
        if (!lastSql.isEmpty()) {
            statements.add(lastSql);
        }

        return statements;
    }

    /**
     * 단일 쿼리 실행
     */
    private SqlResultDto executeSingleQuery(Statement stmt, String sql, String sessionId, long startTime) throws SQLException {
        // SQL 끝의 세미콜론 제거 (JDBC에서는 세미콜론 없이 실행)
        sql = sql.trim();
        if (sql.endsWith(";")) {
            sql = sql.substring(0, sql.length() - 1).trim();
        }

        String queryType = detectQueryType(sql);
        log.debug("SQL 실행: type={}, sql={}", queryType, sql);

        SqlResultDto result = new SqlResultDto();
        result.setSessionId(sessionId);
        result.setQueryType(queryType);

        if ("SELECT".equals(queryType)) {
            ResultSet rs = null;
            try {
                rs = stmt.executeQuery(sql);
                populateSelectResult(result, rs);
            } finally {
                closeQuietly(rs);
            }
        } else {
            int affected = stmt.executeUpdate(sql);
            result.setAffectedRows(affected);
            result.setMessage(queryType + " 실행 완료. 영향받은 행: " + affected);
        }

        result.setSuccess(true);
        result.setExecutionTime(System.currentTimeMillis() - startTime);
        result.setTransactionActive(isTransactionActive(sessionId));

        return result;
    }

    /**
     * 여러 쿼리 순차 실행 (오류 발생 시 중단)
     */
    private SqlResultDto executeMultipleQueries(Statement stmt, List<String> sqlStatements, String sessionId, long startTime) {
        SqlResultDto lastSelectResult = null;
        int totalAffected = 0;
        int successCount = 0;
        StringBuilder messages = new StringBuilder();

        for (int i = 0; i < sqlStatements.size(); i++) {
            String sql = sqlStatements.get(i);
            String queryType = detectQueryType(sql);

            // SQL 끝의 세미콜론 제거 (JDBC에서는 세미콜론 없이 실행)
            sql = sql.trim();
            if (sql.endsWith(";")) {
                sql = sql.substring(0, sql.length() - 1).trim();
            }

            try {
                if ("SELECT".equals(queryType)) {
                    // SELECT는 마지막 결과만 저장
                    ResultSet rs = null;
                    try {
                        rs = stmt.executeQuery(sql);
                        lastSelectResult = new SqlResultDto();
                        lastSelectResult.setSessionId(sessionId);
                        lastSelectResult.setQueryType(queryType);
                        populateSelectResult(lastSelectResult, rs);
                        lastSelectResult.setSuccess(true);
                        successCount++;
                        messages.append("[").append(i + 1).append("/").append(sqlStatements.size()).append("] SELECT 성공\n");
                    } finally {
                        closeQuietly(rs);
                    }
                } else {
                    int affected = stmt.executeUpdate(sql);
                    totalAffected += affected;
                    successCount++;
                    messages.append("[").append(i + 1).append("/").append(sqlStatements.size()).append("] ").append(queryType)
                           .append(" 성공 (").append(affected).append("행)\n");
                }
            } catch (SQLException e) {
                // 오류 발생 시 중단하고 오류 SQL 표시
                log.error("SQL #{} 실행 오류: {}", i + 1, e.getMessage());

                SqlResultDto result = new SqlResultDto();
                result.setSessionId(sessionId);
                result.setQueryType("ERROR");
                result.setAffectedRows(totalAffected);
                result.setSuccess(false);

                // 오류 메시지에 실패한 SQL 포함
                String errorMsg = "[" + (i + 1) + "/" + sqlStatements.size() + "] 오류 발생 - 실행 중단\n\n" +
                                 "성공: " + successCount + "개, 영향받은 행: " + totalAffected + "\n\n" +
                                 messages.toString() + "\n" +
                                 "========== 오류 발생 SQL ==========\n" +
                                 sql + "\n" +
                                 "===================================\n\n" +
                                 "오류: " + e.getMessage();
                result.setMessage(errorMsg);
                result.setExecutionTime(System.currentTimeMillis() - startTime);
                result.setTransactionActive(isTransactionActive(sessionId));
                return result;
            }
        }

        // 모든 SQL 성공
        SqlResultDto result;
        if (lastSelectResult != null) {
            result = lastSelectResult;
            result.setMessage("총 " + sqlStatements.size() + "개 SQL 실행 완료 (영향받은 행: " + totalAffected + ")\n\n" +
                            messages.toString() + "\n" +
                            (lastSelectResult.getMessage() != null ? lastSelectResult.getMessage() : ""));
        } else {
            result = new SqlResultDto();
            result.setSessionId(sessionId);
            result.setQueryType("MULTI");
            result.setAffectedRows(totalAffected);
            result.setSuccess(true);
            result.setMessage("총 " + sqlStatements.size() + "개 SQL 실행 완료 (영향받은 행: " + totalAffected + ")\n\n" + messages.toString());
        }

        result.setExecutionTime(System.currentTimeMillis() - startTime);
        result.setTransactionActive(isTransactionActive(sessionId));

        return result;
    }

    /**
     * SELECT 결과를 DTO에 채움 (컬럼 메타정보 포함)
     */
    private void populateSelectResult(SqlResultDto result, ResultSet rs) throws SQLException {
        ResultSetMetaData metaData = rs.getMetaData();
        int columnCount = metaData.getColumnCount();

        // 컬럼 정보
        List<String> columns = new ArrayList<>();
        List<ColumnInfoDto> columnMetas = new ArrayList<>();
        List<Integer> columnTypes = new ArrayList<>();

        for (int i = 1; i <= columnCount; i++) {
            String colName = metaData.getColumnLabel(i);
            columns.add(colName);

            int colType = metaData.getColumnType(i);
            String typeName = metaData.getColumnTypeName(i);
            columnTypes.add(colType);

            // LOB 타입 여부 확인
            boolean isLobType = colType == Types.CLOB || colType == Types.NCLOB ||
                               colType == Types.BLOB || colType == Types.LONGVARCHAR ||
                               "CLOB".equalsIgnoreCase(typeName) || "NCLOB".equalsIgnoreCase(typeName) ||
                               "BLOB".equalsIgnoreCase(typeName);

            // 컬럼 메타정보 생성 (ResultSetMetaData에서 추출 가능한 정보)
            ColumnInfoDto colMeta = ColumnInfoDto.builder()
                .columnName(colName)
                .dataType(typeName)
                .dataLength(metaData.getPrecision(i))
                .nullable(metaData.isNullable(i) == ResultSetMetaData.columnNullable ? "Y" : "N")
                .columnId(i)
                .lobType(isLobType)
                .build();
            columnMetas.add(colMeta);
        }
        result.setColumns(columns);
        result.setColumnMetas(columnMetas);

        // 데이터 행
        List<Map<String, Object>> rows = new ArrayList<>();
        int rowCount = 0;
        while (rs.next()) {
            Map<String, Object> row = new LinkedHashMap<>();
            for (int i = 1; i <= columnCount; i++) {
                int colType = columnTypes.get(i - 1);
                Object value;

                // CLOB/NCLOB 타입 처리
                if (colType == Types.CLOB || colType == Types.NCLOB || colType == Types.LONGVARCHAR) {
                    value = readClobValue(rs, i);
                } else if (colType == Types.BLOB) {
                    // BLOB은 길이만 표시 (바이너리 데이터)
                    java.sql.Blob blob = rs.getBlob(i);
                    if (blob != null) {
                        value = "[BLOB: " + blob.length() + " bytes]";
                    } else {
                        value = null;
                    }
                } else {
                    value = rs.getObject(i);
                    value = value != null ? value.toString() : null;
                }
                row.put(columns.get(i - 1), value);
            }
            rows.add(row);
            rowCount++;
        }
        result.setRows(rows);
        result.setMessage("조회 완료. 총 " + rowCount + "건");
    }

    /**
     * CLOB 값 읽기
     */
    private String readClobValue(ResultSet rs, int columnIndex) throws SQLException {
        Clob clob = rs.getClob(columnIndex);
        if (clob == null) {
            return null;
        }

        try {
            long length = clob.length();
            if (length == 0) {
                return "";
            }

            // CLOB 전체 읽기 (Reader 사용)
            Reader reader = clob.getCharacterStream();
            StringBuilder sb = new StringBuilder();
            char[] buffer = new char[4096];
            int charsRead;
            while ((charsRead = reader.read(buffer)) != -1) {
                sb.append(buffer, 0, charsRead);
            }
            reader.close();
            return sb.toString();
        } catch (Exception e) {
            log.warn("CLOB 읽기 오류: {}", e.getMessage());
            return "[CLOB 읽기 실패]";
        }
    }

    /**
     * COMMIT
     */
    public SqlResultDto commit(String sessionId) {
        Connection conn = sessionConnections.get(sessionId);
        if (conn == null) {
            return SqlResultDto.error("활성 세션이 없습니다.");
        }

        try {
            conn.commit();
            SqlResultDto result = SqlResultDto.success("COMMIT 완료");
            result.setSessionId(sessionId);
            result.setTransactionActive(true);
            return result;
        } catch (SQLException e) {
            log.error("COMMIT 오류", e);
            return SqlResultDto.error("COMMIT 오류: " + e.getMessage());
        }
    }

    /**
     * ROLLBACK
     */
    public SqlResultDto rollback(String sessionId) {
        Connection conn = sessionConnections.get(sessionId);
        if (conn == null) {
            return SqlResultDto.error("활성 세션이 없습니다.");
        }

        try {
            conn.rollback();
            SqlResultDto result = SqlResultDto.success("ROLLBACK 완료");
            result.setSessionId(sessionId);
            result.setTransactionActive(true);
            return result;
        } catch (SQLException e) {
            log.error("ROLLBACK 오류", e);
            return SqlResultDto.error("ROLLBACK 오류: " + e.getMessage());
        }
    }

    /**
     * 세션 종료 (커넥션 해제)
     */
    public SqlResultDto disconnect(String sessionId) {
        Connection conn = sessionConnections.remove(sessionId);
        sessionLastActivity.remove(sessionId);

        if (conn == null) {
            return SqlResultDto.success("세션이 이미 종료되었습니다.");
        }

        try {
            if (!conn.isClosed()) {
                conn.rollback(); // 미커밋 트랜잭션 롤백
                conn.close();
            }
            log.info("DB 세션 종료: {}", sessionId);
            return SqlResultDto.success("세션 종료 완료");
        } catch (SQLException e) {
            log.error("세션 종료 오류", e);
            return SqlResultDto.error("세션 종료 오류: " + e.getMessage());
        }
    }

    /**
     * 세션별 커넥션 가져오기 또는 생성
     */
    private Connection getOrCreateConnection(SqlRequestDto request) throws SQLException {
        String sessionId = request.getSessionId();

        // 기존 커넥션 확인
        Connection existing = sessionConnections.get(sessionId);
        if (existing != null && !existing.isClosed()) {
            // 마지막 활동 시간 갱신
            updateLastActivity(sessionId);
            return existing;
        }

        // 새 커넥션 생성
        Connection conn;
        if ("DIRECT".equals(request.getDatasourceType())) {
            conn = createDirectConnection(request);
        } else {
            conn = createJndiConnection(request.getJndiName());
        }

        // Auto Commit OFF
        conn.setAutoCommit(false);

        sessionConnections.put(sessionId, conn);
        updateLastActivity(sessionId);
        log.info("새 DB 세션 생성: {}", sessionId);

        return conn;
    }

    /**
     * 세션 활동 시간 갱신
     */
    private void updateLastActivity(String sessionId) {
        sessionLastActivity.put(sessionId, System.currentTimeMillis());
    }

    /**
     * 세션 하트비트 (활성 상태 유지)
     */
    public SqlResultDto heartbeat(String sessionId) {
        Connection conn = sessionConnections.get(sessionId);
        if (conn == null) {
            SqlResultDto result = new SqlResultDto();
            result.setSuccess(false);
            result.setMessage("세션이 없습니다.");
            result.setTransactionActive(false);
            return result;
        }

        try {
            if (conn.isClosed()) {
                sessionConnections.remove(sessionId);
                sessionLastActivity.remove(sessionId);
                SqlResultDto result = new SqlResultDto();
                result.setSuccess(false);
                result.setMessage("커넥션이 종료되었습니다.");
                result.setTransactionActive(false);
                return result;
            }

            // 활동 시간 갱신
            updateLastActivity(sessionId);

            SqlResultDto result = new SqlResultDto();
            result.setSuccess(true);
            result.setSessionId(sessionId);
            result.setTransactionActive(true);
            result.setMessage("세션 활성 상태");
            return result;
        } catch (SQLException e) {
            log.error("하트비트 오류: {}", e.getMessage());
            return SqlResultDto.error("하트비트 오류: " + e.getMessage());
        }
    }

    /**
     * 타임아웃된 세션 정리
     */
    public void cleanupExpiredSessions() {
        long now = System.currentTimeMillis();
        List<String> expiredSessions = new ArrayList<>();

        for (Map.Entry<String, Long> entry : sessionLastActivity.entrySet()) {
            if (now - entry.getValue() > SESSION_TIMEOUT_MS) {
                expiredSessions.add(entry.getKey());
            }
        }

        for (String sessionId : expiredSessions) {
            log.info("타임아웃된 세션 정리: {}", sessionId);
            disconnect(sessionId);
        }
    }

    /**
     * 세션 남은 시간 조회 (초 단위)
     */
    public long getSessionRemainingTime(String sessionId) {
        Long lastActivity = sessionLastActivity.get(sessionId);
        if (lastActivity == null) {
            return 0;
        }
        long elapsed = System.currentTimeMillis() - lastActivity;
        long remaining = SESSION_TIMEOUT_MS - elapsed;
        return Math.max(0, remaining / 1000);
    }

    /**
     * JNDI 커넥션 생성
     * - Tomcat: java:comp/env/ prefix 필요
     * - WebLogic/JBoss: prefix 불필요
     */
    private Connection createJndiConnection(String jndiName) throws SQLException {
        log.info("JNDI 커넥션 생성 시도: jndiName={}", jndiName);

        try {
            InitialContext ctx = new InitialContext();

            // Tomcat인 경우 java:comp/env/ prefix 추가
            String lookupName = jndiName;
            if (ContainerUtil.get() == ContainerUtil.TOMCAT) {
                if (!jndiName.startsWith("java:")) {
                    lookupName = "java:comp/env/" + jndiName;
                }
            }

            log.info("JNDI lookup: {}", lookupName);
            DataSource ds = (DataSource) ctx.lookup(lookupName);
            Connection conn = ds.getConnection();
            log.info("JNDI 커넥션 생성 성공: jndiName={}", jndiName);
            return conn;
        } catch (NamingException e) {
            log.error("JNDI 조회 실패: jndiName={}", jndiName, e);
            throw new SQLException("JNDI 조회 실패: " + jndiName + " (" + e.getMessage() + ")", e);
        } catch (SQLException e) {
            log.error("JNDI 커넥션 생성 실패: jndiName={}", jndiName, e);
            throw e;
        }
    }

    /**
     * 직접 연결 커넥션 생성
     */
    private Connection createDirectConnection(SqlRequestDto request) throws SQLException {
        String driverClassName = request.getDriverClassName();
        if (driverClassName != null && !driverClassName.isEmpty()) {
            try {
                Class.forName(driverClassName);
            } catch (ClassNotFoundException e) {
                throw new SQLException("드라이버 로드 실패: " + driverClassName, e);
            }
        }

        return DriverManager.getConnection(
            request.getJdbcUrl(),
            request.getUsername(),
            request.getPassword()
        );
    }

    /**
     * 쿼리 타입 감지
     */
    private String detectQueryType(String sql) {
        String upper = sql.toUpperCase().trim();

        if (upper.startsWith("SELECT") || upper.startsWith("WITH")) {
            return "SELECT";
        } else if (upper.startsWith("INSERT")) {
            return "INSERT";
        } else if (upper.startsWith("UPDATE")) {
            return "UPDATE";
        } else if (upper.startsWith("DELETE")) {
            return "DELETE";
        } else if (upper.startsWith("CREATE") || upper.startsWith("ALTER") ||
                   upper.startsWith("DROP") || upper.startsWith("TRUNCATE")) {
            return "DDL";
        } else {
            return "OTHER";
        }
    }

    /**
     * 트랜잭션 활성 상태 확인
     */
    private boolean isTransactionActive(String sessionId) {
        Connection conn = sessionConnections.get(sessionId);
        if (conn == null) {
            return false;
        }
        try {
            return !conn.isClosed();
        } catch (SQLException e) {
            return false;
        }
    }

    /**
     * 리소스 조용히 닫기
     */
    private void closeQuietly(AutoCloseable resource) {
        if (resource != null) {
            try {
                resource.close();
            } catch (Exception e) {
                // ignore
            }
        }
    }

    /**
     * 모든 세션 정리 (애플리케이션 종료 시)
     */
    public void cleanupAllSessions() {
        for (String sessionId : sessionConnections.keySet()) {
            disconnect(sessionId);
        }
    }

    /**
     * 스키마 목록 조회 (Oracle)
     */
    public List<String> getSchemas(SqlRequestDto request) {
        List<String> schemas = new ArrayList<>();
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            conn = getOrCreateConnection(request);
            stmt = conn.createStatement();

            // Oracle 스키마(사용자) 목록 조회
            String sql = "SELECT username FROM all_users ORDER BY username";
            rs = stmt.executeQuery(sql);

            while (rs.next()) {
                schemas.add(rs.getString("username"));
            }
        } catch (SQLException e) {
            log.error("스키마 목록 조회 오류: {}", e.getMessage());
        } finally {
            closeQuietly(rs);
            closeQuietly(stmt);
        }

        return schemas;
    }

    /**
     * 테이블 목록 조회 (Oracle)
     */
    public List<TableInfoDto> getTables(SqlRequestDto request) {
        List<TableInfoDto> tables = new ArrayList<>();
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            conn = getOrCreateConnection(request);
            stmt = conn.createStatement();

            String schema = request.getSchemaName();
            if (schema == null || schema.isEmpty()) {
                // 현재 사용자의 테이블
                schema = conn.getMetaData().getUserName();
            }

            // Oracle 테이블 목록 조회 (코멘트 포함)
            String sql = "SELECT t.table_name, 'TABLE' as table_type, c.comments " +
                         "FROM all_tables t " +
                         "LEFT JOIN all_tab_comments c ON t.owner = c.owner AND t.table_name = c.table_name " +
                         "WHERE t.owner = '" + schema.toUpperCase() + "' " +
                         "UNION ALL " +
                         "SELECT v.view_name, 'VIEW' as table_type, c.comments " +
                         "FROM all_views v " +
                         "LEFT JOIN all_tab_comments c ON v.owner = c.owner AND v.view_name = c.table_name " +
                         "WHERE v.owner = '" + schema.toUpperCase() + "' " +
                         "ORDER BY table_name";

            rs = stmt.executeQuery(sql);

            while (rs.next()) {
                tables.add(TableInfoDto.builder()
                    .tableName(rs.getString("table_name"))
                    .tableType(rs.getString("table_type"))
                    .comments(rs.getString("comments"))
                    .build());
            }
        } catch (SQLException e) {
            log.error("테이블 목록 조회 오류: {}", e.getMessage());
        } finally {
            closeQuietly(rs);
            closeQuietly(stmt);
        }

        return tables;
    }

    /**
     * 테이블 컬럼 정보 조회 (Oracle) - PK, Index, UK, FK 정보 포함
     */
    public List<ColumnInfoDto> getColumns(SqlRequestDto request) {
        List<ColumnInfoDto> columns = new ArrayList<>();
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            conn = getOrCreateConnection(request);
            stmt = conn.createStatement();

            String schema = request.getSchemaName();
            if (schema == null || schema.isEmpty()) {
                schema = conn.getMetaData().getUserName();
            }
            String tableName = request.getTableName();
            String schemaUpper = schema.toUpperCase();
            String tableUpper = tableName.toUpperCase();

            // Oracle 컬럼 정보 조회 (PK, UK, FK, Index 포함)
            String sql = "SELECT c.column_name, c.data_type, c.data_length, c.nullable, " +
                         "c.data_default, cc.comments, c.column_id, " +
                         "CASE WHEN pk.column_name IS NOT NULL THEN 'Y' ELSE 'N' END as is_pk, " +
                         "CASE WHEN uk.column_name IS NOT NULL THEN 'Y' ELSE 'N' END as is_uk, " +
                         "CASE WHEN fk.column_name IS NOT NULL THEN 'Y' ELSE 'N' END as is_fk, " +
                         "idx.index_names " +
                         "FROM all_tab_columns c " +
                         "LEFT JOIN all_col_comments cc ON c.owner = cc.owner AND c.table_name = cc.table_name AND c.column_name = cc.column_name " +
                         // PK 조인
                         "LEFT JOIN (SELECT acc.owner, acc.table_name, acc.column_name " +
                         "           FROM all_cons_columns acc " +
                         "           JOIN all_constraints ac ON acc.constraint_name = ac.constraint_name AND acc.owner = ac.owner " +
                         "           WHERE ac.constraint_type = 'P') pk " +
                         "ON c.owner = pk.owner AND c.table_name = pk.table_name AND c.column_name = pk.column_name " +
                         // UK 조인
                         "LEFT JOIN (SELECT acc.owner, acc.table_name, acc.column_name " +
                         "           FROM all_cons_columns acc " +
                         "           JOIN all_constraints ac ON acc.constraint_name = ac.constraint_name AND acc.owner = ac.owner " +
                         "           WHERE ac.constraint_type = 'U') uk " +
                         "ON c.owner = uk.owner AND c.table_name = uk.table_name AND c.column_name = uk.column_name " +
                         // FK 조인
                         "LEFT JOIN (SELECT acc.owner, acc.table_name, acc.column_name " +
                         "           FROM all_cons_columns acc " +
                         "           JOIN all_constraints ac ON acc.constraint_name = ac.constraint_name AND acc.owner = ac.owner " +
                         "           WHERE ac.constraint_type = 'R') fk " +
                         "ON c.owner = fk.owner AND c.table_name = fk.table_name AND c.column_name = fk.column_name " +
                         // Index 조인 (인덱스명 콤마로 연결)
                         "LEFT JOIN (SELECT ic.table_owner, ic.table_name, ic.column_name, " +
                         "           LISTAGG(ic.index_name, ', ') WITHIN GROUP (ORDER BY ic.index_name) as index_names " +
                         "           FROM all_ind_columns ic " +
                         "           WHERE ic.table_owner = '" + schemaUpper + "' AND ic.table_name = '" + tableUpper + "' " +
                         "           GROUP BY ic.table_owner, ic.table_name, ic.column_name) idx " +
                         "ON c.owner = idx.table_owner AND c.table_name = idx.table_name AND c.column_name = idx.column_name " +
                         "WHERE c.owner = '" + schemaUpper + "' " +
                         "AND c.table_name = '" + tableUpper + "' " +
                         "ORDER BY c.column_id";

            rs = stmt.executeQuery(sql);

            while (rs.next()) {
                String indexNames = rs.getString("index_names");
                columns.add(ColumnInfoDto.builder()
                    .columnName(rs.getString("column_name"))
                    .dataType(rs.getString("data_type"))
                    .dataLength(rs.getInt("data_length"))
                    .nullable(rs.getString("nullable"))
                    .defaultValue(rs.getString("data_default"))
                    .comments(rs.getString("comments"))
                    .columnId(rs.getInt("column_id"))
                    .primaryKey("Y".equals(rs.getString("is_pk")))
                    .uniqueKey("Y".equals(rs.getString("is_uk")))
                    .foreignKey("Y".equals(rs.getString("is_fk")))
                    .indexed(indexNames != null && !indexNames.isEmpty())
                    .indexName(indexNames)
                    .build());
            }
        } catch (SQLException e) {
            log.error("컬럼 정보 조회 오류: {}", e.getMessage());
        } finally {
            closeQuietly(rs);
            closeQuietly(stmt);
        }

        return columns;
    }

    /**
     * 테이블 DDL 조회 (Oracle) - 컬럼 정보 기반으로 DDL 생성
     */
    public String getTableDdl(SqlRequestDto request) {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            conn = getOrCreateConnection(request);
            stmt = conn.createStatement();

            String schema = request.getSchemaName();
            if (schema == null || schema.isEmpty()) {
                schema = conn.getMetaData().getUserName();
            }
            String tableName = request.getTableName();
            String schemaUpper = schema.toUpperCase();
            String tableUpper = tableName.toUpperCase();

            StringBuilder ddl = new StringBuilder();
            ddl.append("CREATE TABLE ").append(schemaUpper).append(".").append(tableUpper).append(" (\n");

            // 컬럼 정보 조회
            String colSql = "SELECT c.column_name, c.data_type, c.data_length, c.data_precision, c.data_scale, " +
                           "c.nullable, c.data_default, cc.comments " +
                           "FROM all_tab_columns c " +
                           "LEFT JOIN all_col_comments cc ON c.owner = cc.owner AND c.table_name = cc.table_name AND c.column_name = cc.column_name " +
                           "WHERE c.owner = '" + schemaUpper + "' AND c.table_name = '" + tableUpper + "' " +
                           "ORDER BY c.column_id";

            rs = stmt.executeQuery(colSql);
            List<String> columnDefs = new ArrayList<>();
            List<String> columnComments = new ArrayList<>();

            while (rs.next()) {
                StringBuilder colDef = new StringBuilder();
                String colName = rs.getString("column_name");
                String dataType = rs.getString("data_type");
                int dataLength = rs.getInt("data_length");
                int dataPrecision = rs.getInt("data_precision");
                int dataScale = rs.getInt("data_scale");
                String nullable = rs.getString("nullable");
                String defaultVal = rs.getString("data_default");
                String comment = rs.getString("comments");

                colDef.append("    ").append(colName).append(" ");

                // 데이터 타입
                if ("NUMBER".equals(dataType)) {
                    if (dataPrecision > 0) {
                        colDef.append(dataType).append("(").append(dataPrecision);
                        if (dataScale > 0) {
                            colDef.append(",").append(dataScale);
                        }
                        colDef.append(")");
                    } else {
                        colDef.append(dataType);
                    }
                } else if ("VARCHAR2".equals(dataType) || "CHAR".equals(dataType) || "NVARCHAR2".equals(dataType)) {
                    colDef.append(dataType).append("(").append(dataLength).append(")");
                } else if ("RAW".equals(dataType)) {
                    colDef.append(dataType).append("(").append(dataLength).append(")");
                } else {
                    colDef.append(dataType);
                }

                // DEFAULT
                if (defaultVal != null && !defaultVal.trim().isEmpty()) {
                    colDef.append(" DEFAULT ").append(defaultVal.trim());
                }

                // NOT NULL
                if ("N".equals(nullable)) {
                    colDef.append(" NOT NULL");
                }

                columnDefs.add(colDef.toString());

                // 컬럼 코멘트
                if (comment != null && !comment.trim().isEmpty()) {
                    columnComments.add("COMMENT ON COLUMN " + schemaUpper + "." + tableUpper + "." + colName +
                                      " IS '" + comment.replace("'", "''") + "';");
                }
            }
            closeQuietly(rs);

            // PK 제약조건 조회
            String pkSql = "SELECT acc.column_name FROM all_cons_columns acc " +
                          "JOIN all_constraints ac ON acc.constraint_name = ac.constraint_name AND acc.owner = ac.owner " +
                          "WHERE ac.owner = '" + schemaUpper + "' AND ac.table_name = '" + tableUpper + "' " +
                          "AND ac.constraint_type = 'P' ORDER BY acc.position";
            rs = stmt.executeQuery(pkSql);
            List<String> pkCols = new ArrayList<>();
            while (rs.next()) {
                pkCols.add(rs.getString("column_name"));
            }
            closeQuietly(rs);

            // DDL 조합
            ddl.append(String.join(",\n", columnDefs));
            if (!pkCols.isEmpty()) {
                ddl.append(",\n    CONSTRAINT ").append(tableUpper).append("_PK PRIMARY KEY (")
                   .append(String.join(", ", pkCols)).append(")");
            }
            ddl.append("\n);\n");

            // 테이블 코멘트
            String tblCommentSql = "SELECT comments FROM all_tab_comments WHERE owner = '" + schemaUpper +
                                  "' AND table_name = '" + tableUpper + "'";
            rs = stmt.executeQuery(tblCommentSql);
            if (rs.next()) {
                String tblComment = rs.getString("comments");
                if (tblComment != null && !tblComment.trim().isEmpty()) {
                    ddl.append("\nCOMMENT ON TABLE ").append(schemaUpper).append(".").append(tableUpper)
                       .append(" IS '").append(tblComment.replace("'", "''")).append("';\n");
                }
            }
            closeQuietly(rs);

            // 컬럼 코멘트
            if (!columnComments.isEmpty()) {
                ddl.append("\n").append(String.join("\n", columnComments)).append("\n");
            }

            // 인덱스 조회
            String idxSql = "SELECT i.index_name, i.uniqueness, " +
                           "LISTAGG(ic.column_name, ', ') WITHIN GROUP (ORDER BY ic.column_position) as columns " +
                           "FROM all_indexes i " +
                           "JOIN all_ind_columns ic ON i.owner = ic.index_owner AND i.index_name = ic.index_name " +
                           "WHERE i.table_owner = '" + schemaUpper + "' AND i.table_name = '" + tableUpper + "' " +
                           "AND i.index_name NOT IN (SELECT constraint_name FROM all_constraints WHERE owner = '" + schemaUpper + "' AND table_name = '" + tableUpper + "' AND constraint_type = 'P') " +
                           "GROUP BY i.index_name, i.uniqueness ORDER BY i.index_name";
            rs = stmt.executeQuery(idxSql);
            boolean hasIndex = false;
            while (rs.next()) {
                if (!hasIndex) {
                    ddl.append("\n");
                    hasIndex = true;
                }
                String idxName = rs.getString("index_name");
                String uniqueness = rs.getString("uniqueness");
                String idxColumns = rs.getString("columns");
                ddl.append("CREATE ");
                if ("UNIQUE".equals(uniqueness)) {
                    ddl.append("UNIQUE ");
                }
                ddl.append("INDEX ").append(schemaUpper).append(".").append(idxName)
                   .append(" ON ").append(schemaUpper).append(".").append(tableUpper)
                   .append(" (").append(idxColumns).append(");\n");
            }

            return ddl.toString();

        } catch (SQLException e) {
            log.error("DDL 조회 오류: {}", e.getMessage());
            return "-- DDL 조회 실패: " + e.getMessage();
        } finally {
            closeQuietly(rs);
            closeQuietly(stmt);
        }
    }
}
