package go.kr.project.system.execquery.service;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import go.kr.project.system.execquery.config.IsolatedTransactionManager;
import go.kr.project.system.execquery.exception.TransactionException;
import go.kr.project.system.execquery.model.QueryRequest;
import go.kr.project.system.execquery.model.QueryResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;

@Slf4j
@RequiredArgsConstructor
@Service
public class DbQueryService {

    private final IsolatedTransactionManager transactionManager;

    // 결과 수 제한 상수 추가
    private static final int MAX_RESULT_COUNT = 10000;


    /**
     * SELECT 쿼리 실행
     */
    public QueryResponse executeSelect(QueryRequest request) {
        QueryResponse response = new QueryResponse();
        response.setQueryType("SELECT");

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            // SELECT 쿼리인지 검증
            String query = request.getQuery().toUpperCase();
            if (containsDataModificationStatements(query)) {
                response.setSuccess(false);
                response.setMessage("SELECT 쿼리 타입으로 INSERT, UPDATE, DELETE 쿼리를 실행할 수 없습니다.");
                return response;
            }

            conn = transactionManager.getConnection(request.getTransactionId());
            // SELECT 쿼리는 읽기 전용으로 설정 (트랜잭션이 없는 경우에만)
            if (request.getTransactionId() == null || request.getTransactionId().isEmpty()) {
                conn.setReadOnly(true);
            }
            ps = conn.prepareStatement(request.getQuery());
            rs = ps.executeQuery();

            List<Map<String, Object>> resultList = new ArrayList<>();
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();

            int rowCount = 0;
            boolean resultLimited = false;

            while (rs.next()) {

                // 최대 결과 수 체크
                if (rowCount >= MAX_RESULT_COUNT) {
                    resultLimited = true;
                    break;
                }

                Map<String, Object> row = new LinkedHashMap<>();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i);
                    int columnType = metaData.getColumnType(i);
                    Object value = rs.getObject(i);

                    // DATE 타입인 경우
                    if (columnType == Types.DATE && value != null) {
                        Date dateValue = rs.getDate(i);
                        if (dateValue != null) {
                            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                            value = dateFormat.format(dateValue);
                        }
                    }
                    // TIMESTAMP 또는 DATETIME 타입인 경우
                    else if ((columnType == Types.TIMESTAMP || columnType == Types.TIMESTAMP_WITH_TIMEZONE) && value != null) {
                        Timestamp timestampValue = rs.getTimestamp(i);
                        if (timestampValue != null) {
                            SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                            value = dateTimeFormat.format(timestampValue);
                        }
                    }

                    row.put(columnName, value);
                }

                resultList.add(row);
                rowCount++;

            }

            response.setSuccess(true);
            response.setResult(resultList);
            response.setRowsAffected(resultList.size());

            // 결과가 제한되었을 경우 메시지 추가
            if (resultLimited) {
                response.setMessage("결과가 너무 많아 " + MAX_RESULT_COUNT + "건만 표시합니다.");
            }

            if (request.getTransactionId() != null && !request.getTransactionId().isEmpty()) {
                response.setTransactionId(request.getTransactionId());
            }

        } catch (SQLException e) {
            log.error("SELECT 쿼리 실행 중 오류: {}", e.getMessage(), e);
            response.setSuccess(false);
            response.setMessage("쿼리 실행 중 오류가 발생했습니다: " + e.getMessage());
            if (request.getTransactionId() != null && !request.getTransactionId().isEmpty()) {
                response.setTransactionId(request.getTransactionId());
            }
        } finally {
            closeResources(rs, ps, conn, request.getTransactionId() != null);
        }

        return response;
    }

    /**
     * 데이터 변경 쿼리(INSERT, UPDATE, DELETE) 실행
     */
    public QueryResponse executeUpdate(QueryRequest request) {
        QueryResponse response = new QueryResponse();
        response.setQueryType(request.getQueryType());

        Connection conn = null;
        PreparedStatement ps = null;

        try {
            // 트랜잭션ID가 없으면 새로 생성
            if (request.getTransactionId() == null || request.getTransactionId().isEmpty()) {
                String transactionId = transactionManager.beginTransaction();
                request.setTransactionId(transactionId);
                response.setTransactionId(transactionId);
            } else {
                response.setTransactionId(request.getTransactionId());
            }

            conn = transactionManager.getConnection(request.getTransactionId());
            ps = conn.prepareStatement(request.getQuery());

            int rowsAffected = ps.executeUpdate();

            response.setSuccess(true);
            response.setMessage("쿼리가 성공적으로 실행되었습니다. 변경사항은 커밋하기 전까지 임시 저장됩니다.");
            response.setRowsAffected(rowsAffected);

        } catch (SQLException e) {
            log.error("{} 쿼리 실행 중 오류: {}", request.getQueryType(), e.getMessage(), e);
            response.setSuccess(false);
            response.setMessage("쿼리 실행 중 오류가 발생했습니다: " + e.getMessage());
            response.setTransactionId(request.getTransactionId());
        } finally {
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    log.error("PreparedStatement 닫기 오류", e);
                }
            }
        }

        return response;
    }

    /**
     * 트랜잭션 커밋
     */
    public QueryResponse executeCommit(QueryRequest request) {
        QueryResponse response = new QueryResponse();
        response.setQueryType("COMMIT");

        try {
            if (request.getTransactionId() == null || request.getTransactionId().isEmpty()) {
                response.setSuccess(false);
                response.setMessage("커밋할 트랜잭션 ID가 필요합니다.");
                return response;
            }

            transactionManager.commitTransaction(request.getTransactionId());

            response.setSuccess(true);
            response.setMessage("트랜잭션이 성공적으로 커밋되었습니다.");

        } catch (TransactionException e) {
            log.error("트랜잭션 커밋 권한 오류: {}", e.getMessage(), e);
            response.setSuccess(false);
            response.setMessage(e.getMessage());
            response.setTransactionId(request.getTransactionId());
        } catch (SQLException e) {
            log.error("트랜잭션 커밋 중 오류: {}", e.getMessage(), e);
            response.setSuccess(false);
            response.setMessage("트랜잭션 커밋 중 오류가 발생했습니다: " + e.getMessage());
            response.setTransactionId(request.getTransactionId());
        }

        return response;
    }

    /**
     * 트랜잭션 롤백
     */
    public QueryResponse executeRollback(QueryRequest request) {
        QueryResponse response = new QueryResponse();
        response.setQueryType("ROLLBACK");

        try {
            if (request.getTransactionId() == null || request.getTransactionId().isEmpty()) {
                response.setSuccess(false);
                response.setMessage("롤백할 트랜잭션 ID가 필요합니다.");
                return response;
            }

            transactionManager.rollbackTransaction(request.getTransactionId());

            response.setSuccess(true);
            response.setMessage("트랜잭션이 성공적으로 롤백되었습니다.");

        } catch (TransactionException e) {
            log.error("트랜잭션 롤백 권한 오류: {}", e.getMessage(), e);
            response.setSuccess(false);
            response.setMessage(e.getMessage());
            response.setTransactionId(request.getTransactionId());
        } catch (SQLException e) {
            log.error("트랜잭션 롤백 중 오류: {}", e.getMessage(), e);
            response.setSuccess(false);
            response.setMessage("트랜잭션 롤백 중 오류가 발생했습니다: " + e.getMessage());
            response.setTransactionId(request.getTransactionId());
        }

        return response;
    }

    /**
     * 쿼리 타입에 따라 적절한 실행 메서드 호출
     */
    public QueryResponse executeQuery(QueryRequest request) {
        String queryType = request.getQueryType().toUpperCase();

        // ObjectMapper 생성 및 설정
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);

        // 요청 객체를 다시 파싱 (필요한 경우)
        // 예: 요청 객체의 쿼리 문자열에 줄바꿈이 포함되어 있을 경우
        String requestJson = null;
        QueryRequest sanitizedRequest = null;
        try {
            requestJson = objectMapper.writeValueAsString(request);
            sanitizedRequest = objectMapper.readValue(requestJson, QueryRequest.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        switch (queryType) {
            case "SELECT":
                return executeSelect(sanitizedRequest);
            case "INSERT":
            case "UPDATE":
            case "DELETE":
                return executeUpdate(sanitizedRequest);
            case "COMMIT":
                return executeCommit(request);
            case "ROLLBACK":
                return executeRollback(request);
            default:
                QueryResponse response = new QueryResponse();
                response.setSuccess(false);
                response.setMessage("지원하지 않는 쿼리 타입입니다. SELECT, INSERT, UPDATE, DELETE, COMMIT, ROLLBACK만 지원합니다.");
                return response;
        }
    }

    /**
     * 쿼리에 데이터 수정 구문(INSERT, UPDATE, DELETE)이 포함되어 있는지 확인
     */
    private boolean containsDataModificationStatements(String query) {
        // 정규식을 사용하여 INSERT, UPDATE, DELETE 키워드가 단어 경계에 있는지 확인
        return query.matches("(?i).*\\bINSERT\\b.*") || 
               query.matches("(?i).*\\bUPDATE\\b.*") || 
               query.matches("(?i).*\\bDELETE\\b.*");
    }

    /**
     * 리소스 정리
     */
    private void closeResources(ResultSet rs, PreparedStatement ps, Connection conn, boolean keepConnection) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.error("ResultSet 닫기 오류", e);
            }
        }

        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                log.error("PreparedStatement 닫기 오류", e);
            }
        }

        if (conn != null && !keepConnection) {
            try {
                // 연결을 닫기 전에 읽기 전용 모드를 해제
                if (conn.isReadOnly()) {
                    conn.setReadOnly(false);
                }
                conn.close();
            } catch (SQLException e) {
                log.error("Connection 닫기 오류", e);
            }
        }
    }
}
