package com.eactive.eai.rms.onl.common.service.ums;

import com.eactive.apim.portal.template.entity.MessageRequest;

import edu.emory.mathcs.backport.java.util.Collections;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;


@Slf4j
@Service
@Transactional(transactionManager = "transactionManagerForEMS")
public class UmsDispatchService {

    private final ThreadPoolExecutor executorService;
    private final TransactionTemplate transactionTemplate;

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 20;
    private static final int QUEUE_CAPACITY = 1000;
    private static final long KEEP_ALIVE_TIME = 60L;

    @PersistenceContext(unitName = "entityManagerFactoryForEMS")
    private EntityManager entityManager;

    @Autowired
    private UmsSender umsSender;

    @Autowired
    private StandardMessageFactory messageFactory;

    @Autowired
    public UmsDispatchService(
            @Qualifier("transactionManagerForEMS") PlatformTransactionManager transactionManager) {
        this.executorService = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        this.transactionTemplate = new TransactionTemplate(transactionManager);
        log.debug("UmsDispatchService initialized with thread pool - core: {}, max: {}, queue: {}",
                CORE_POOL_SIZE, MAX_POOL_SIZE, QUEUE_CAPACITY);
    }

    @SuppressWarnings("unchecked")
	@Transactional(transactionManager = "transactionManagerForEMS")
    public void processMessages() {
        log.info("Starting to process pending messages");

        // 상태가 PENDING인 메시지들을 조회하고 바로 PROCESSING으로 변경    
        List<MessageRequest> pendingMessages = entityManager.createQuery(
                        "SELECT m FROM MessageRequest m WHERE m.requestStatus = :status " +
                                "ORDER BY m.requestDate ASC")
                .setParameter("status", "PENDING")
                .setMaxResults(100)
                .getResultList();
        		
        if ( !pendingMessages.isEmpty()) {
            log.info("Found {} pending messages, updating status to PROCESSING", pendingMessages.size());

            // 조회된 메시지들의 상태를 일괄 PROCESSING으로 변경
            entityManager.createQuery(
                            "UPDATE MessageRequest m SET m.requestStatus = :newStatus " +
                                    "WHERE m IN :messages")
                    .setParameter("newStatus", "PROCESSING")
                    .setParameter("messages", pendingMessages)
                    .executeUpdate();

            // 영속성 컨텍스트 초기화 (벌크 연산 후)
            entityManager.flush();
            entityManager.clear();

            // 상태가 변경된 메시지들을 다시 조회
            List<MessageRequest> messagesToProcess = entityManager.createQuery(
                            "SELECT m FROM MessageRequest m WHERE m IN :messages")
                    .setParameter("messages", pendingMessages)
                    .getResultList();

            log.info("Starting to submit {} messages for processing", messagesToProcess.size());
            for (MessageRequest message : messagesToProcess) {
                submitMessageTask(message);
            }
        } else {
            log.info("No pending messages found");
        }
    }


    private void submitMessageTask(MessageRequest message) {
        int queueSize = executorService.getQueue().size();
        log.debug("Current queue size before submission: {}", queueSize);

        executorService.submit(() -> {
            try {
                transactionTemplate.execute(status -> {
                    try {
                        processMessage(message);
                        return null;
                    } catch (Exception e) {
                        log.error("Failed to process message: {} - Error: {}",
                                message.getId(), e.getMessage(), e);
                        updateMessageStatus(message, "FAILED");
                        status.setRollbackOnly();
                        return null;
                    }
                });
            } catch (Exception e) {
                log.error("Transaction execution failed for message: {} - Error: {}",
                        message.getId(), e.getMessage(), e);
            }
        });
    }

    public void processMessage(MessageRequest message) throws IOException {
        try {
            String standardMessage = messageFactory.createMessage(message);
            log.debug("Processing message ID: {} - Type: {}", message.getId(), message.getMessageType());

            umsSender.sendMessage(standardMessage);
            updateMessageStatus(message, "SENT");
            log.debug("Successfully processed and sent message ID: {}", message.getId());
        } catch (Exception e) {
            log.error("Message processing failed for ID: {} - Error: {}",
                    message.getId(), e.getMessage(), e);
            updateMessageStatus(message, "FAILED");
            throw e;
        }
    }

    public void updateMessageStatus(MessageRequest message, String status) {
        message.setRequestStatus(status);
        message.setSentDate(LocalDateTime.now());
        entityManager.merge(message);
    }
}