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

import go.kr.project.system.execquery.exception.TransactionException;
import go.kr.project.system.execquery.model.TransactionInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class IsolatedTransactionManager {

    private final DataSource queryDataSource;
    private final Map<String, TransactionInfo> transactions = new ConcurrentHashMap<>();

    private long transactionTimeout; // 트랜잭션 타임아웃 (밀리초)

    public IsolatedTransactionManager(@Qualifier("dataSource") DataSource queryDataSource,
                                    @Value("${query-executor.transaction.timeout-minutes:5}") int timeoutMinutes) {
        this.queryDataSource = queryDataSource;
        // yml에서 분 단위로 설정된 값을 밀리초로 변환
        this.transactionTimeout = timeoutMinutes * 60 * 1000L; // 분 -> 밀리초 변환
        log.info("쿼리 실행기 - 트랜잭션 타임아웃 설정: {}분 ({}ms)", timeoutMinutes, this.transactionTimeout);
    }

    /**
     * 새 트랜잭션을 시작하고 트랜잭션 ID를 반환
     */
    public String beginTransaction() throws SQLException {
        String transactionId = UUID.randomUUID().toString();
        String sessionId = getCurrentSessionId();

        Connection conn = queryDataSource.getConnection();
        conn.setAutoCommit(false);

        TransactionInfo info = new TransactionInfo();
        info.setConnection(conn);
        info.setLastAccessTime(System.currentTimeMillis());
        info.setCreationTime(System.currentTimeMillis());
        info.setSessionId(sessionId);

        transactions.put(transactionId, info);
        log.debug("트랜잭션 시작: {}, 세션: {}", transactionId, sessionId);
        return transactionId;
    }

    /**
     * 트랜잭션 ID로 커넥션을 반환
     */
    public Connection getConnection(String transactionId) throws SQLException {
        if (transactionId == null || transactionId.isEmpty()) {
            return beginNewConnection();
        }

        if (!transactions.containsKey(transactionId)) {
            throw new TransactionException("존재하지 않는 트랜잭션 ID: " + transactionId);
        }

        TransactionInfo info = transactions.get(transactionId);
        String currentSessionId = getCurrentSessionId();

        // 세션 ID가 일치하는지 확인
        if (!currentSessionId.equals(info.getSessionId())) {
            throw new TransactionException("트랜잭션 접근 권한이 없습니다. 다른 세션에서 생성된 트랜잭션입니다.");
        }

        info.setLastAccessTime(System.currentTimeMillis());
        return info.getConnection();
    }

    /**
     * 자동 커밋이 활성화된 새 커넥션 반환 (SELECT 전용)
     */
    private Connection beginNewConnection() throws SQLException {
        Connection conn = queryDataSource.getConnection();
        conn.setAutoCommit(true);
        return conn;
    }

    /**
     * 트랜잭션 커밋
     */
    public void commitTransaction(String transactionId) throws SQLException {
        if (!transactions.containsKey(transactionId)) {
            throw new TransactionException("존재하지 않는 트랜잭션 ID: " + transactionId);
        }

        TransactionInfo info = transactions.get(transactionId);
        String currentSessionId = getCurrentSessionId();

        if (!currentSessionId.equals(info.getSessionId())) {
            throw new TransactionException("트랜잭션 접근 권한이 없습니다. 다른 세션에서 생성된 트랜잭션입니다.");
        }

        try {
            info.getConnection().commit();
            log.debug("트랜잭션 커밋: {}", transactionId);
            closeAndRemove(transactionId);
        } catch (SQLException e) {
            log.error("트랜잭션 커밋 중 오류 발생: {}", e.getMessage(), e);
            throw e;
        }
    }

    /**
     * 트랜잭션 롤백
     */
    public void rollbackTransaction(String transactionId) throws SQLException {
        if (!transactions.containsKey(transactionId)) {
            throw new TransactionException("존재하지 않는 트랜잭션 ID: " + transactionId);
        }

        TransactionInfo info = transactions.get(transactionId);
        String currentSessionId = getCurrentSessionId();

        if (!currentSessionId.equals(info.getSessionId())) {
            throw new TransactionException("트랜잭션 접근 권한이 없습니다. 다른 세션에서 생성된 트랜잭션입니다.");
        }

        try {
            info.getConnection().rollback();
            log.debug("트랜잭션 롤백: {}", transactionId);
            closeAndRemove(transactionId);
        } catch (SQLException e) {
            log.error("트랜잭션 롤백 중 오류 발생: {}", e.getMessage(), e);
            throw e;
        }
    }

    /**
     * 트랜잭션 종료 및 제거
     */
    private void closeAndRemove(String transactionId) {
        TransactionInfo info = transactions.get(transactionId);
        if (info != null) {
            try {
                info.getConnection().close();
            } catch (SQLException e) {
                log.error("커넥션 닫기 중 오류 발생: {}", e.getMessage(), e);
            } finally {
                transactions.remove(transactionId);
            }
        }
    }

    /**
     * 현재 세션 ID 반환
     */
    private String getCurrentSessionId() {
        try {
            ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            HttpServletRequest request = attr.getRequest();
            HttpSession session = request.getSession(true);
            return session.getId();
        } catch (Exception e) {
            log.warn("세션 ID 가져오기 실패: {}", e.getMessage());
            return "unknown-session";
        }
    }

    /**
     * 유휴 트랜잭션 정리 (1분마다 실행)
     */
    @Scheduled(fixedRate = 60000)
    public void cleanupIdleTransactions() {
        long currentTime = System.currentTimeMillis();

        transactions.entrySet().removeIf(entry -> {
            TransactionInfo info = entry.getValue();
            boolean isTimeout = (currentTime - info.getLastAccessTime() > transactionTimeout);

            if (isTimeout) {
                try {
                    info.getConnection().rollback();
                    info.getConnection().close();
                    log.info("유휴 트랜잭션 정리: {}, 세션: {}, 경과 시간: {}ms",
                            entry.getKey(),
                            info.getSessionId(),
                            currentTime - info.getCreationTime());
                    return true;
                } catch (SQLException e) {
                    log.error("유휴 트랜잭션 정리 중 오류 발생: {}", e.getMessage(), e);
                }
            }
            return false;
        });
    }

    /**
     * 특정 세션의 모든 트랜잭션 롤백 및 정리
     */
    public void cleanupSessionTransactions(String sessionId) {
        if (sessionId == null) return;

        transactions.entrySet().removeIf(entry -> {
            TransactionInfo info = entry.getValue();
            if (sessionId.equals(info.getSessionId())) {
                try {
                    info.getConnection().rollback();
                    info.getConnection().close();
                    log.info("세션 종료로 인한 트랜잭션 정리: {}", entry.getKey());
                    return true;
                } catch (SQLException e) {
                    log.error("세션 트랜잭션 정리 중 오류 발생: {}", e.getMessage(), e);
                }
            }
            return false;
        });
    }

}
