package org.jpos.tcpay.service;

import org.jpos.iso.ISOMsg;
import org.jpos.tcpay.ApplicationProperties;
import org.jpos.tcpay.db.entity.*;
import org.jpos.tcpay.db.repository.*;
import org.jpos.util.AppLogger;
import org.jpos.util.StringParsingUtil;

import javax.persistence.*;
import javax.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class TransactionAuxUtils {

    public final EntityManagerFactory emf;
    private final AppLogger logger = new AppLogger();
    
    // JPA Repositories
    private final PosTempTransactionRepository tempTransactionRepository;
    private final PosTransactionRepository transactionRepository;
    private final PosFailedTransactionRepository failedTransactionRepository;
    private final PosTransactionReversalRepository reversalRepository;

    public TransactionAuxUtils() {
        logger.log(new ApplicationProperties().getPersistenceProperties());
        emf = Persistence.createEntityManagerFactory("jpos-tcpay-local", new ApplicationProperties().getPersistenceProperties());
        
        // Initialize repositories
        this.tempTransactionRepository = new PosTempTransactionRepositoryImpl(emf);
        this.transactionRepository = new PosTransactionRepositoryImpl(emf);
        this.failedTransactionRepository = new PosFailedTransactionRepositoryImpl(emf);
        this.reversalRepository = new PosTransactionReversalRepositoryImpl(emf);
    }
    // Get AcquirerTerminal by POSTerminal


    public PosTerminal getPosTerminalByTerminalId(String posTerminalId) {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<PosTerminal> terminalQuery = entityManager.createQuery("Select post from PosTerminal post where post.terminalid= :terminalId", PosTerminal.class);
            terminalQuery.setParameter("terminalId", posTerminalId);

            return terminalQuery.getSingleResult();
        } catch (Exception e) {
            logger.log("No reference for POS Terminal " + posTerminalId);
            return null;
        } finally {
            entityManager.close();
        }
    }

    public AcquirerConnection getAcquirerConnectionByAcquirer(Acquirer acquirer) {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<AcquirerConnection> query = entityManager.createQuery("Select conn from AcquirerConnection conn where conn.acquirer= :acquirer", AcquirerConnection.class);
            query.setParameter("acquirer", acquirer);

            return query.getSingleResult();
        } catch (Exception e) {
            logger.log("No reference for Acquirer Connection " + acquirer);
            return null;
        } finally {
            entityManager.close();
        }
    }

    public Acquirer getAcquirerByAcquirerId(Long acquirerId) {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<Acquirer> query = entityManager.createQuery(
                    "SELECT acquirer FROM Acquirer acquirer WHERE acquirer.id = :acquirerId",
                    Acquirer.class
            );
            query.setParameter("acquirerId", acquirerId);

            return query.getSingleResult();
        } catch (Exception e) {
            logger.log("No reference for Acquirer Connection with ID: " + acquirerId);
            return null;
        } finally {
            entityManager.close();
        }
    }

    public AcquirerConnection getAcquirerConnectionByAcquirerId(Long acquirerId) {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<AcquirerConnection> query = entityManager.createQuery(
                "SELECT conn FROM AcquirerConnection conn WHERE conn.acquirer.id = :acquirerId", 
                AcquirerConnection.class
            );
            query.setParameter("acquirerId", acquirerId);

            return query.getSingleResult();
        } catch (Exception e) {
            logger.log("No reference for Acquirer Connection with ID: " + acquirerId);
            return null;
        } finally {
            entityManager.close();
        }
    }
    /**
     * Check if terminal is busy by looking for existing entry in pos_temp_transaction
     * @param bTid Bank Terminal ID
     * @param bMid Bank Merchant ID  
     * @param acquirerId bank/Acquirer ID
     * @return true if terminal is busy, false otherwise
     */
    public boolean isTerminalBusy(String bTid, String bMid, Long acquirerId) {
        try {
            return tempTransactionRepository.existsByBankTidAndBankMidAndAcquirerId(bTid, bMid, acquirerId);
        } catch (Exception e) {
            logger.log("Error checking terminal busy status: " + e.getMessage());
            return false;
        }
    }

    /**
     * Insert transaction into temp table before sending to bank
     * @param tempTransaction the temporary transaction to insert
     * @return the persisted entity with generated ID
     * @throws RuntimeException if terminal is busy or insertion fails
     */
    public PosTempTransaction insertTempTransaction(PosTempTransaction tempTransaction) {
        try {
            // Double-check terminal is not busy
            if (isTerminalBusy(tempTransaction.getBTid(), tempTransaction.getBMid(), tempTransaction.getAcquirerId())) {
                throw new RuntimeException("Terminal is busy");
            }

            return tempTransactionRepository.save(tempTransaction);

        } catch (Exception e) {
            throw new RuntimeException("Failed to insert temp transaction: " + e.getMessage(), e);
        }
    }

    /**
     * Move transaction from temp to success table atomically
     * @param tempTransaction ID of the temp transaction
     * @param successTransaction the success transaction to insert
     */
    public void moveTempToSuccess(PosTempTransaction tempTransaction, PosTransaction successTransaction) {
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();

        try {
            transaction.begin();

            // Insert success transaction using repository
            transactionRepository.save(successTransaction);

            // Remove temp transaction using repository
            if (Objects.nonNull(tempTransaction)) {
                tempTransactionRepository.delete(tempTransaction);
                logger.log("Moved transaction to success table for terminal " + tempTransaction.getBTid());
            } else {
                logger.log("Warning: Temp transaction " + tempTransaction + " not found during success move");
            }

            transaction.commit();

        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Error moving temp to success: " + e.getMessage());
            throw new RuntimeException("Failed to move temp to success: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }
    /**
     * Move transaction from temp to success table atomically
     * @param tempTransactionId ID of the temp transaction
     * @param successTransaction the success transaction to insert
     */
    public void moveTempToSuccess(Long tempTransactionId, PosTransaction successTransaction) {
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        
        try {
            transaction.begin();
            
            // Insert success transaction using repository
            transactionRepository.save(successTransaction);
            
            // Remove temp transaction using repository
            Optional<PosTempTransaction> tempTxnOpt = tempTransactionRepository.findById(tempTransactionId);
            if (tempTxnOpt.isPresent()) {
                tempTransactionRepository.deleteById(tempTransactionId);
                logger.log("Moved transaction to success table for terminal " + tempTxnOpt.get().getBTid());
            } else {
                logger.log("Warning: Temp transaction " + tempTransactionId + " not found during success move");
            }
            
            transaction.commit();
            
        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Error moving temp to success: " + e.getMessage());
            throw new RuntimeException("Failed to move temp to success: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }

    public void moveTempToFailed(PosTempTransaction tempTxn, PosFailedTransaction failedTransaction) {
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();

        try {
            transaction.begin();

            // Insert failed transaction
            entityManager.persist(failedTransaction);

            // Remove temp transaction
            if (tempTxn != null) {
                tempTransactionRepository.delete(tempTxn);
                //entityManager.remove(tempTxn);
                logger.log("Moved transaction to failed table for terminal " + tempTxn.getBTid());
            } else {
                logger.log("Warning: Temp transaction " + tempTxn + " not found during failed move");
            }

            transaction.commit();

        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Error moving temp to failed: " + e.getMessage());
            throw new RuntimeException("Failed to move temp to failed: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }

    /**
     * Move transaction from temp to failed table atomically
     * @param tempTransactionId ID of the temp transaction
     * @param failedTransaction the failed transaction to insert
     */
    public void moveTempToFailed(Long tempTransactionId, PosFailedTransaction failedTransaction) {
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        
        try {
            transaction.begin();
            
            // Insert failed transaction
            entityManager.persist(failedTransaction);
            
            // Remove temp transaction
            PosTempTransaction tempTxn = entityManager.find(PosTempTransaction.class, tempTransactionId);
            if (tempTxn != null) {
                tempTransactionRepository.delete(tempTxn);
                //entityManager.remove(tempTxn);
                logger.log("Moved transaction to failed table for terminal " + tempTxn.getBTid());
            } else {
                logger.log("Warning: Temp transaction " + tempTransactionId + " not found during failed move");
            }
            
            transaction.commit();
            
        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Error moving temp to failed: " + e.getMessage());
            throw new RuntimeException("Failed to move temp to failed: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }

    /**
     * Get temp transaction by ID
     * @param tempTransactionId ID of the temp transaction
     * @return the temp transaction if found, null otherwise
     */
    public PosTempTransaction getTempTransactionById(Long tempTransactionId) {
        try {
            return tempTransactionRepository.findById(tempTransactionId).orElse(null);
        } catch (Exception e) {
            logger.log("Error getting temp transaction by ID: " + e.getMessage());
            return null;
        }
    }

    /**
     * Get temp transaction by terminal and merchant IDs
     * @param bTid Bank Terminal ID
     * @param bMid Bank Merchant ID
     * @return the temp transaction if found, null otherwise
     */
    public PosTempTransaction getTempTransactionByTerminal(String bTid, String bMid) {
        try {
            return tempTransactionRepository.findByBankTidAndBankMid(bTid, bMid).orElse(null);
        } catch (Exception e) {
            logger.log("Error getting temp transaction: " + e.getMessage());
            return null;
        }
    }

    /**
     * Create PosTempTransaction from ISO message and terminal info
     * @param request The ISO request message
     * @param posTerminal The POS terminal
     * @param acquirerTerminal The acquirer terminal
     * @return populated PosTempTransaction
     */
    public PosTempTransaction createTempTransactionFromISO(ISOMsg request, PosTerminal posTerminal, AcquirerTerminal acquirerTerminal) {
        PosTempTransaction tempTxn = new PosTempTransaction();
        
        // Set source (POS) terminal info
        tempTxn.setSTid(posTerminal.getTerminalid())
               .setSMid(posTerminal.getPosMerchant().getMerchantid());
        
        // Set bank (acquirer) terminal info
        tempTxn.setBTid(acquirerTerminal.getTerminalId())
                .setBMid(acquirerTerminal.getAcquirerMerchant().getMerchantId())
                .setAcquirerId(acquirerTerminal.getAcquirer().getId());
        
        try {
            // Extract ISO fields
            tempTxn.setMti(request.getMTI());
            tempTxn.setProcCode(request.getString(3));
            
            // Amounts
            if (request.hasField(4)) {
                tempTxn.setTotalAmount(new BigDecimal(request.getString(4)).divide(new BigDecimal(100)));
            }
            
            // STAN, Invoice, Batch numbers
            tempTxn.setSTidStan(request.getString(11));
            tempTxn.setBTidStan(request.getString(11)); // Initially same as source
            
            // Date/Time info can be extracted from fields 12/13 if needed
            tempTxn.setBTidDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
            tempTxn.setBTidTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss")));
            tempTxn.setEntryMode(request.getString(22)); // POS Entry Mode
            tempTxn.setConditionCode(request.getString(25)); // POS Condition Code
            tempTxn.setCurrencyCode(request.getString(49)); // Currency Code

            // Card related info
            if (request.hasField(2)) {
                String pan = request.getString(2);
                tempTxn.setEncryptedPan(pan); // TODO: Need to encrypt PAN
                tempTxn.setEncryptedExpiry(request.getString(14));
                // Mask PAN keeping first 6 and last 4 digits
                if (pan.length() >= 10) {
                    String masked = StringParsingUtil.maskCardNumber(pan);
                    tempTxn.setMaskedCardNo(masked);
                }
            }
            
            // Track 2 data (if present and encrypted)
            if (request.hasField(35)) {
                tempTxn.setEncryptedTrack2(request.getString(35)); // TODO: Need to encrypt TRACK2
            }
            
            // POS Data
            if (request.hasField(23)) {
                // POS entry mode
                tempTxn.setPanSeq(request.getString(23));
            }
            
            // Additional data
            if (request.hasField(55)) {
                tempTxn.setEmvData(request.getString(55));
            }
            
            // MCC from merchant
            tempTxn.setMccCode(acquirerTerminal.getAcquirerMerchant().getMcc());
            
        } catch (Exception e) {
            logger.log("Error extracting ISO fields: " + e.getMessage());
            logger.log(e);
        }
        
        return tempTxn;
    }

    /**
     * Create PosTransaction from PosTempTransaction and response
     * @param tempTransaction The temp transaction
     * @param response The ISO response message
     * @return populated PosTransaction
     */
    public PosTransaction createSuccessTransactionFromTemp(PosTempTransaction tempTransaction, ISOMsg response) {
        PosTransaction successTxn = new PosTransaction();
        
        // Copy all fields from temp transaction
        copyTransactionFields(tempTransaction, successTxn);
        
        try {
            // Update with response data
            if (response.hasField(37)) {
                successTxn.setReferenceNo(response.getString(37));
            }
            if (response.hasField(38)) {
                successTxn.setApprovalCode(response.getString(38));
            }
            if (response.hasField(39)) {
                successTxn.setResponseCode(response.getString(39));
            }
            // Add more response field mapping as needed
            
        } catch (Exception e) {
            logger.log("Error extracting response fields: " + e.getMessage());
        }
        
        return successTxn;
    }

    /**
     * Create PosFailedTransaction from PosTempTransaction and response
     * @param tempTransaction The temp transaction
     * @param response The ISO response message (may be null for timeouts)
     * @return populated PosFailedTransaction
     */
    public PosFailedTransaction createFailedTransactionFromTemp(PosTempTransaction tempTransaction, ISOMsg response) {
        PosFailedTransaction failedTxn = new PosFailedTransaction();
        
        // Copy all fields from temp transaction
        copyTransactionFields(tempTransaction, failedTxn);
        
        try {
            if (response != null) {
                // Update with response data
                if (response.hasField(37)) {
                    failedTxn.setReferenceNo(response.getString(37));
                }
                if (response.hasField(39)) {
                    failedTxn.setResponseCode(response.getString(39));
                }
                // Set error message if available
                if (response.hasField(63)) {
                    failedTxn.setResponseMessage(response.getString(63));
                }
            } else {
                // Timeout or connection error
                failedTxn.setResponseCode("91"); // No response from issuer
                failedTxn.setResponseMessage("Communication timeout");
            }
            
        } catch (Exception e) {
            logger.log("Error creating failed transaction: " + e.getMessage());
            failedTxn.setResponseCode("96"); // System error
            failedTxn.setResponseMessage("System processing error");
        }
        
        return failedTxn;
    }

    /**
     * Helper method to copy common fields between transaction types
     */
    private void copyTransactionFields(PosTempTransaction source, Object target) {
        if (target instanceof PosTransaction) {
            PosTransaction dest = (PosTransaction) target;
            dest.setSTid(source.getSTid())
                .setSMid(source.getSMid())
                .setSTidStan(source.getSTidStan())
                .setSTidInvoiceNo(source.getSTidInvoiceNo())
                .setSTidBatchNo(source.getSTidBatchNo())
                .setBTid(source.getBTid())
                .setBMid(source.getBMid())
                .setAcquirerId(source.getAcquirerId())
                .setBTidStan(source.getBTidStan())
                .setBTidInvoiceNo(source.getBTidInvoiceNo())
                .setBTidBatchNo(source.getBTidBatchNo())
                .setBTidDate(source.getBTidDate())
                .setBTidTime(source.getBTidTime())
                .setEntryMode(source.getEntryMode())
                .setConditionCode(source.getConditionCode())
                .setCurrencyCode(source.getCurrencyCode())
                .setMti(source.getMti())
                .setProcCode(source.getProcCode())
                .setTotalAmount(source.getTotalAmount())
                .setAuthAmount(source.getAuthAmount())
                .setCashAmount(source.getCashAmount())
                .setTipAmount(source.getTipAmount())
                .setMccCode(source.getMccCode())
                .setEncryptedTrack2(source.getEncryptedTrack2())
                .setEncryptedExpiry(source.getEncryptedExpiry())
                .setEncryptedPan(source.getEncryptedPan())
                .setMaskedCardNo(source.getMaskedCardNo())
                .setPanSeq(source.getPanSeq())
                .setEmvData(source.getEmvData())
                .setAcquirerReferenceNo(source.getAcquirerReferenceNo())
                .setSchemeReferenceNo(source.getSchemeReferenceNo())
                .setMetadata(source.getMetadata());
        } else if (target instanceof PosFailedTransaction) {
            PosFailedTransaction dest = (PosFailedTransaction) target;
            dest.setSTid(source.getSTid())
                .setSMid(source.getSMid())
                .setSTidStan(source.getSTidStan())
                .setSTidInvoiceNo(source.getSTidInvoiceNo())
                .setSTidBatchNo(source.getSTidBatchNo())
                .setBTid(source.getBTid())
                .setBMid(source.getBMid())
                .setAcquirerId(source.getAcquirerId())
                .setBTidStan(source.getBTidStan())
                .setBTidInvoiceNo(source.getBTidInvoiceNo())
                .setBTidDate(source.getBTidDate())
                .setBTidTime(source.getBTidTime())
                .setEntryMode(source.getEntryMode())
                .setConditionCode(source.getConditionCode())
                .setCurrencyCode(source.getCurrencyCode())
                .setBTidBatchNo(source.getBTidBatchNo())
                .setMti(source.getMti())
                .setProcCode(source.getProcCode())
                .setTotalAmount(source.getTotalAmount())
                .setAuthAmount(source.getAuthAmount())
                .setCashAmount(source.getCashAmount())
                .setTipAmount(source.getTipAmount())
                .setMccCode(source.getMccCode())
                .setEncryptedTrack2(source.getEncryptedTrack2())
                .setEncryptedPan(source.getEncryptedPan())
                .setMaskedCardNo(source.getMaskedCardNo())
                .setEncryptedExpiry(source.getEncryptedExpiry())
                .setPanSeq(source.getPanSeq())
                .setEmvData(source.getEmvData())
                .setAcquirerReferenceNo(source.getAcquirerReferenceNo())
                .setSchemeReferenceNo(source.getSchemeReferenceNo())
                .setMetadata(source.getMetadata());
        }
    }

    /**
     * Cleanup orphaned temp transactions older than specified minutes
     * This is useful for maintenance and error recovery
     * @param maxAgeMinutes Maximum age of temp transactions in minutes
     * @return number of transactions cleaned up
     */
    public int cleanupOrphanedTempTransactions(int maxAgeMinutes) {
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        
        try {
            transaction.begin();
            
            // Find temp transactions older than maxAgeMinutes
            String jpql = "SELECT t FROM PosTempTransaction t WHERE t.createdDateTime < :cutoffTime";
            TypedQuery<PosTempTransaction> query = entityManager.createQuery(jpql, PosTempTransaction.class);
            
            // Calculate cutoff time
            java.time.LocalDateTime cutoffTime = java.time.LocalDateTime.now().minusMinutes(maxAgeMinutes);
            query.setParameter("cutoffTime", cutoffTime);
            
            List<PosTempTransaction> orphanedTransactions = query.getResultList();
            
            int cleanedUp = 0;
            for (PosTempTransaction tempTxn : orphanedTransactions) {
                // Move to failed table
                PosFailedTransaction failedTxn = createFailedTransactionFromTemp(tempTxn, null);
                failedTxn.setResponseCode("91"); // System timeout
                failedTxn.setResponseMessage("Transaction timeout - cleaned up by maintenance");
                
                entityManager.persist(failedTxn);
                entityManager.remove(tempTxn);
                cleanedUp++;
                
                logger.log("Cleaned up orphaned temp transaction ID: " + tempTxn.getId() + 
                          " for terminal: " + tempTxn.getBTid());
            }
            
            transaction.commit();
            
            if (cleanedUp > 0) {
                logger.log("Cleanup completed: " + cleanedUp + " orphaned temp transactions moved to failed table");
            }
            
            return cleanedUp;
            
        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Error during temp transaction cleanup: " + e.getMessage());
            throw new RuntimeException("Failed to cleanup orphaned temp transactions: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }

    /**
     * Get statistics about transaction tables
     * @return TransactionStatistics object with counts
     */
    public TransactionStatistics getTransactionStatistics() {
        EntityManager entityManager = emf.createEntityManager();
        try {
            // Count temp transactions
            Long tempCount = entityManager.createQuery(
                "SELECT COUNT(t) FROM PosTempTransaction t", Long.class)
                .getSingleResult();
            
            // Count successful transactions (last 24 hours)
            Long successCount24h = entityManager.createQuery(
                "SELECT COUNT(t) FROM PosTransaction t WHERE t.createdDateTime >= :yesterday", Long.class)
                .setParameter("yesterday", java.time.LocalDateTime.now().minusDays(1))
                .getSingleResult();
            
            // Count failed transactions (last 24 hours)
            Long failedCount24h = entityManager.createQuery(
                "SELECT COUNT(t) FROM PosFailedTransaction t WHERE t.createdDateTime >= :yesterday", Long.class)
                .setParameter("yesterday", java.time.LocalDateTime.now().minusDays(1))
                .getSingleResult();
            
            return new TransactionStatistics(tempCount, successCount24h, failedCount24h);
            
        } catch (Exception e) {
            logger.log("Error getting transaction statistics: " + e.getMessage());
            return new TransactionStatistics(0L, 0L, 0L);
        } finally {
            entityManager.close();
        }
    }

    /**
     * Simple statistics holder class
     */
    public static class TransactionStatistics {
        public final long tempTransactionCount;
        public final long successfulTransactions24h;
        public final long failedTransactions24h;
        
        public TransactionStatistics(long tempCount, long successCount, long failedCount) {
            this.tempTransactionCount = tempCount;
            this.successfulTransactions24h = successCount;
            this.failedTransactions24h = failedCount;
        }
        
        @Override
        public String toString() {
            return "TransactionStatistics{" +
                   "tempTransactions=" + tempTransactionCount +
                   ", successful24h=" + successfulTransactions24h +
                   ", failed24h=" + failedTransactions24h +
                   ", successRate=" + getSuccessRate() + "%" +
                   '}';
        }
        
        public double getSuccessRate() {
            long total = successfulTransactions24h + failedTransactions24h;
            if (total == 0) return 0.0;
            return (double) successfulTransactions24h / total * 100.0;
        }
    }

    // =================== REVERSAL MANAGEMENT METHODS ===================

    /**
     * Find temp transaction by bank terminal and merchant IDs
     * @param bankTid Bank Terminal ID
     * @param bankMid Bank Merchant ID
     * @return the temp transaction if found, null otherwise
     */
    public PosTempTransaction findTempByBankTidMid(String bankTid, String bankMid) {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<PosTempTransaction> query = entityManager.createQuery(
                "SELECT t FROM PosTempTransaction t WHERE t.bTid = :bTid AND t.bMid = :bMid", 
                PosTempTransaction.class
            );
            query.setParameter("bTid", bankTid);
            query.setParameter("bMid", bankMid);
            
            List<PosTempTransaction> results = query.getResultList();
            return results.isEmpty() ? null : results.get(0);
            
        } catch (Exception e) {
            logger.log("Error finding temp transaction by bank TID/MID: " + e.getMessage());
            return null;
        } finally {
            entityManager.close();
        }
    }

    /**
     * Update temp transaction status
     * @param tempTxnId ID of the temp transaction
     * @param status new status to set
     */
    public void updateTempTransactionStatus(Long tempTxnId, PosTempTransaction.TempTransactionStatus status) {
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        
        try {
            transaction.begin();
            
            PosTempTransaction tempTxn = entityManager.find(PosTempTransaction.class, tempTxnId);
            if (tempTxn != null) {
                tempTxn.setStatus(status);
                entityManager.merge(tempTxn);
                logger.log("Updated temp transaction " + tempTxnId + " status to: " + status);
            } else {
                logger.log("Warning: Temp transaction " + tempTxnId + " not found for status update");
            }
            
            transaction.commit();
            
        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Error updating temp transaction status: " + e.getMessage());
            throw new RuntimeException("Failed to update temp transaction status: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }

    /**
     * Create reversal request from temp transaction
     * @param tempTransaction the original temp transaction
     * @param reason reason for reversal
     * @return populated PosTransactionReversal
     */
    public PosTransactionReversal createReversalRequest(PosTempTransaction tempTransaction, String reason) {
        PosTransactionReversal reversal = new PosTransactionReversal();
        
        reversal.setOriginalTempTxnId(tempTransaction.getId())
               .setReversalReason(reason)
               .setSTid(tempTransaction.getSTid())
               .setSMid(tempTransaction.getSMid())
               .setSTidStan(tempTransaction.getSTidStan())
               .setBTid(tempTransaction.getBTid())
               .setBMid(tempTransaction.getBMid())
               .setAcquirerId(tempTransaction.getAcquirerId())
               .setBTidStan(tempTransaction.getBTidStan())
               .setOriginalMti(tempTransaction.getMti())
               .setOriginalEncryptedPan(tempTransaction.getEncryptedPan())
               .setOriginalEncryptedExpiryDate(tempTransaction.getEncryptedExpiry())
               .setOriginalProcCode(tempTransaction.getProcCode())
               .setOriginalStan(tempTransaction.getBTidStan())
               .setOriginalTime(tempTransaction.getBTidTime())
               .setOriginalDate(tempTransaction.getBTidDate())
               .setOriginalEntryMode(tempTransaction.getEntryMode())
               .setOriginalPanSequence(tempTransaction.getPanSeq())
               .setOriginalCurrencyCode(tempTransaction.getCurrencyCode())
               .setOriginalAmount(tempTransaction.getTotalAmount())
               .setOriginalBatchNo(tempTransaction.getBTidBatchNo())
               .setOriginalReferenceNo(tempTransaction.getReferenceNo());
        return reversal;
    }

    /**
     * Save reversal record to database
     * @param reversal the reversal to save
     * @return the persisted reversal with generated ID
     */
    public PosTransactionReversal saveReversalRecord(PosTransactionReversal reversal) {
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        
        try {
            transaction.begin();
            if (Objects.nonNull(reversal.getId())) {
                entityManager.merge(reversal);
            } else  {
                entityManager.persist(reversal);
            }
            transaction.commit();
            
            logger.log("Saved reversal record for temp txn: " + reversal.getOriginalTempTxnId());
            return reversal;
            
        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Error saving reversal record: " + e.getMessage());
            throw new RuntimeException("Failed to save reversal record: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }

    /**
     * Delete temp transaction by ID
     * @param tempTxnId ID of the temp transaction to delete
     */
    public void deleteTempTransaction(Long tempTxnId) {
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        
        try {
            transaction.begin();
            
            PosTempTransaction tempTxn = entityManager.find(PosTempTransaction.class, tempTxnId);
            if (tempTxn != null) {
                entityManager.remove(tempTxn);
                logger.log("Deleted temp transaction: " + tempTxnId);
            } else {
                logger.log("Warning: Temp transaction " + tempTxnId + " not found for deletion");
            }
            
            transaction.commit();
            
        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Error deleting temp transaction: " + e.getMessage());
            throw new RuntimeException("Failed to delete temp transaction: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }

    /**
     * Check if reversal already exists for a temp transaction
     * @param tempTransaction the temp transaction to check
     * @return true if reversal exists, false otherwise
     */
    public boolean reversalExists(PosTempTransaction tempTransaction) {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(r) FROM PosTransactionReversal r WHERE r.originalTempTxnId = :tempTxnId", 
                Long.class
            );
            query.setParameter("tempTxnId", tempTransaction.getId());
            
            Long count = query.getSingleResult();
            return count > 0;
            
        } catch (Exception e) {
            logger.log("Error checking reversal existence: " + e.getMessage());
            return false;
        } finally {
            entityManager.close();
        }
    }

    /**
     * Count pending reversals for monitoring
     * @return number of pending reversals
     */
    public int countPendingReversals() {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(r) FROM PosTransactionReversal r WHERE r.reversalStatus IN (:statuses)", 
                Long.class
            );
            query.setParameter("statuses", Arrays.asList(
                    PosTransactionReversal.ReversalStatus.PENDING,
                    PosTransactionReversal.ReversalStatus.SENT,
                    PosTransactionReversal.ReversalStatus.RETRY_SCHEDULED
            ));
            
            Long count = query.getSingleResult();
            return count.intValue();
            
        } catch (Exception e) {
            logger.log("Error counting pending reversals: " + e.getMessage());
            return 0;
        } finally {
            entityManager.close();
        }
    }

    /**
     * Count stale temp transactions for monitoring
     * @param maxAgeMinutes Maximum age in minutes
     * @return number of stale temp transactions
     */
    public int countStaleTemps(int maxAgeMinutes) {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<Long> query = entityManager.createQuery(
                "SELECT COUNT(t) FROM PosTempTransaction t WHERE t.createdDateTime < :cutoffTime", 
                Long.class
            );
            
            java.time.LocalDateTime cutoffTime = java.time.LocalDateTime.now().minusMinutes(maxAgeMinutes);
            query.setParameter("cutoffTime", cutoffTime);
            
            Long count = query.getSingleResult();
            return count.intValue();
            
        } catch (Exception e) {
            logger.log("Error counting stale temp transactions: " + e.getMessage());
            return 0;
        } finally {
            entityManager.close();
        }
    }

    /**
     * Find temp transactions older than specified age
     * @param maxAgeMinutes Maximum age in minutes
     * @return list of stale temp transactions
     */
    public List<PosTempTransaction> findStaleTemps(int maxAgeMinutes) {
        EntityManager entityManager = emf.createEntityManager();
        try {
            TypedQuery<PosTempTransaction> query = entityManager.createQuery(
                "SELECT t FROM PosTempTransaction t WHERE t.createdDateTime < :cutoffTime ORDER BY t.createdDateTime", 
                PosTempTransaction.class
            );
            
            java.time.LocalDateTime cutoffTime = java.time.LocalDateTime.now().minusMinutes(maxAgeMinutes);
            query.setParameter("cutoffTime", cutoffTime);
            
            return query.getResultList();
            
        } catch (Exception e) {
            logger.log("Error finding stale temp transactions: " + e.getMessage());
            return new ArrayList<>();
        } finally {
            entityManager.close();
        }
    }

    /**
     * Atomic operation to cleanup temp with reversal record
     * @param tempTransaction the temp transaction
     * @param reversal the reversal record
     * @param reversalResponse the reversal response message
     */
    @Transactional
    public void atomicTempCleanupWithReversalRecord(
            PosTempTransaction tempTransaction, 
            PosTransactionReversal reversal, 
            ISOMsg reversalResponse) {
        
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        
        try {
            transaction.begin();
            
            // 1. Update reversal record with response
            if (reversalResponse != null) {
                reversal.setReversalResponse(reversalResponse.toString());
                try {
                    String responseCode = reversalResponse.getString(39);
                    reversal.setReversalResponseCode(responseCode);
                    if (reversalResponse.hasField(37)) {
                        reversal.setReversalReferenceNo(reversalResponse.getString(37));
                    }
                } catch (Exception e) {
                    logger.log("Error extracting reversal response fields: " + e.getMessage());
                }
            }
            
            reversal.setReversalStatus(PosTransactionReversal.ReversalStatus.COMPLETED);
            reversal.setCompletedDateTime(java.time.LocalDateTime.now());
            
            // 2. Save/update reversal record
            if (reversal.getId() == null) {
                entityManager.persist(reversal);
            } else {
                entityManager.merge(reversal);
            }
            
            // 3. Remove from temp table
            PosTempTransaction tempTxn = entityManager.find(PosTempTransaction.class, tempTransaction.getId());
            if (tempTxn != null) {
                entityManager.remove(tempTxn);
            }
            
            transaction.commit();
            
            // 4. Log completion
            logger.log("Reversal completed and temp cleaned: " + tempTransaction.getBTid());
            
        } catch (Exception e) {
            if (transaction.isActive()) {
                transaction.rollback();
            }
            logger.log("Failed atomic cleanup operation: " + e.getMessage());
            throw new RuntimeException("Failed atomic cleanup operation: " + e.getMessage(), e);
        } finally {
            entityManager.close();
        }
    }

/*
    public AcquirerTerminal getAcquirerTerminalByTerminalId(String posTerminalId) {
        return getDummmyAcquirerTerminal();
    }

    private AcquirerTerminal  getDummmyAcquirerTerminal() {
        AcquirerTerminal acquirerTerminal = new AcquirerTerminal();

        acquirerTerminal.setTerminalId("12345678").setAcquirerMerchant(getDummyAcquirerMerchant())
                .setAcquirer(getDummyAcquirer());

        return acquirerTerminal;
    }


    private AcquirerConnection getDummmyAcquirerConnection(Acquirer acquirer) {
        AcquirerConnection acquirerConnection = new AcquirerConnection();
        acquirerConnection.setAcquirer(getDummyAcquirer())
                .setIpAddress("testssl.tnsi.com.au").setPortNumber(45257).setSsl(true)
                .setKeyStorePath(null).setKeyStorePassword(null)
                .setTrustStorePath("cfg/fiserv/truststore.jks").setTrustStorePassword("password")
                .setTpdu("6000412000").setLengthMode(LengthMode.HEX);


        return acquirerConnection;
    }

    private Acquirer getDummyAcquirer() {
        Acquirer acq = new Acquirer();
        acq.setName(AcquirerNames.FISERV.getName()).setAcquirerInstitutionCode("").setNii("41");

        return acq;
    }

    private AcquirerMerchant getDummyAcquirerMerchant() {
        AcquirerMerchant acquirerMerchant = new AcquirerMerchant();
        acquirerMerchant.setAcquirer(getDummyAcquirer()).setMerchantId("123456781234567")
                .setMcc("5799").setAddress(getDummyAddress());

        return acquirerMerchant;
    }

    private Address getDummyAddress() {
        Address address = new Address();
        address.setLine1("Line1").setLine2("Line2").setLine3("Line3").setLine4("Line4")
                .setCity("Bangalore").setState("Karnataka").setCountry("India").setPostalCode("560100");

        return address;
    }*/
}
