package org.jpos.tcpay;

import org.jpos.iso.ISOMsg;
import org.jpos.tcpay.acquirer.JposAcquirerTranslator;
import org.jpos.tcpay.acquirer.JposAcquirerTranslatorFactory;
import org.jpos.tcpay.connection.AcquirerChannel;
import org.jpos.tcpay.connection.AcquirerChannelFactory;
import org.jpos.tcpay.constant.RouterConstants;
import org.jpos.tcpay.db.entity.*;
import org.jpos.tcpay.service.ReversalService;
import org.jpos.tcpay.service.ReversalMetrics;
import org.jpos.tcpay.service.TransactionAuxUtils;
import org.jpos.tcpay.model.IsoAcquirerTerminal;
import org.jpos.tcpay.service.TransactionLifecycleService;
import org.jpos.util.AppLogger;

import java.util.*;

public class TransactionRouter {
    int[] SUPPORTED_FIELDS = {3,4,5,9,11,12,13,22,23,24,25,37,38,39,41,42,44,49,54,55,60,62,63};
    private AppLogger logger = new AppLogger();

    private final TransactionAuxUtils transactionAuxUtils = new TransactionAuxUtils();
    private final ReversalService reversalService = new ReversalService(transactionAuxUtils);
    private final ReversalMetrics reversalMetrics = new ReversalMetrics(transactionAuxUtils);
    
    public ISOMsg handleRequest(ISOMsg request) {
        PosTempTransaction tempTransaction = null;
        long startTime = System.currentTimeMillis();
        TransactionManagerImpl transactionManager = new TransactionManagerImpl(transactionAuxUtils,
                new TransactionLifecycleService(transactionAuxUtils, reversalService), reversalService);
        try {

            // Validate the Request
                // Check the mandatory fields and mapping of the Acquirer Terminal
                // Is the Terminal Busy
                // Get Acquirer and Acquirer Connection, Jpos Translator
            boolean result = transactionManager.checkAndValidateRequest(request);
            if(!result) {
                logger.log("Request validation failed");
                return generateErrorMsg(request);
            }

            // Start the txn
                // Trickle feed the offline txns
                // Create and store temp transaction
            result = transactionManager.onboardRequest(request);
            if(!result) {
                logger.log("Request onboarding failed");
                return generateErrorMsg(request);
            }

            // Handle the result
            ISOMsg response =  transactionManager.processTransaction(request);
            if (Objects.isNull(response)) {
                return generateErrorMsg(request);
            }
            return response;

            /*// Extract DE-41 (stid) from message
            String stid = request.getString(41);
            if (stid == null || stid.trim().isEmpty()) {
                logger.log("Missing or empty DE-41 (stid) field");
                return generateErrorMsg(request);
            }

            // Identify the POS Terminal using DE-41
            PosTerminal posTerminal = transactionAuxUtils.getPosTerminalByTerminalId(stid);
            if (Objects.isNull(posTerminal)) {
                logger.log("No Matching PosTerminal for stid: " + stid);
                return generateErrorMsg(request);
            }

            // Get First acquirer terminal (in practice, you might want more sophisticated selection)
            if (posTerminal.getAcquirerTerminals().isEmpty()) {
                logger.log("No AcquirerTerminals configured for POS terminal: " + stid);
                return generateErrorMsg(request);
            }
            
            AcquirerTerminal acquirerTerminal = posTerminal.getAcquirerTerminals().get(0);
            
            // Get the bank terminal ID and merchant ID for busy check
            String bankTid = acquirerTerminal.getTerminalId();
            String bankMid = acquirerTerminal.getAcquirerMerchant().getMerchantId();
            
            // ===== ENHANCED REVERSAL-AWARE TERMINAL BUSY CHECK =====
            
            // Check and handle stale transactions before processing new request
            if (!reversalService.checkAndHandleStaleTransactions(bankTid, bankMid)) {
                logger.log("Stale transaction cleanup in progress for terminal: " + bankTid);
                ISOMsg busyResponse = generateErrorMsg(request);
                busyResponse.set(39, "76"); // Blocked terminal - cleanup in progress
                return busyResponse;
            }
            
            // Check if terminal is busy
            if (transactionAuxUtils.isTerminalBusy(bankTid, bankMid)) {
                logger.log("Terminal is busy - Bank TID: " + bankTid + ", Bank MID: " + bankMid);
                ISOMsg busyResponse = generateErrorMsg(request);
                busyResponse.set(39, "76"); // Blocked terminal
                return busyResponse;
            }

            // Create temp transaction and insert it
            tempTransaction = transactionAuxUtils.createTempTransactionFromISO(request, posTerminal, acquirerTerminal);
            tempTransaction = transactionAuxUtils.insertTempTransaction(tempTransaction);
            
            logger.log("Created temp transaction with ID: " + tempTransaction.getId() + " for terminal: " + bankTid);

            // Get Respective Acquirer instance
            Acquirer acquirer = acquirerTerminal.getAcquirer();
            AcquirerConnection acquirerConnection = transactionAuxUtils.getAcquirerConnectionByAcquirer(acquirer);
            if (Objects.isNull(acquirerConnection)) {
                logger.log("No Matching AcquirerConnection for acquirer: " + acquirer.getName());
                // Move temp to failed before returning error
                PosFailedTransaction failedTxn = transactionAuxUtils.createFailedTransactionFromTemp(tempTransaction, null);
                failedTxn.setResponseMessage("No acquirer connection available");
                transactionAuxUtils.moveTempToFailed(tempTransaction.getId(), failedTxn);
                return generateErrorMsg(request);
            }

            // Translate the request
            JposAcquirerTranslator translator = JposAcquirerTranslatorFactory.getJposAcquirerTranslator(acquirer.getName());
            if (Objects.isNull(translator)) {
                logger.log("No Matching JposAcquirerTranslator for acquirer: " + acquirer.getName());
                // Move temp to failed before returning error
                PosFailedTransaction failedTxn = transactionAuxUtils.createFailedTransactionFromTemp(tempTransaction, null);
                failedTxn.setResponseMessage("No translator available");
                transactionAuxUtils.moveTempToFailed(tempTransaction.getId(), failedTxn);
                return generateErrorMsg(request);
            }

            AcquirerChannel acquirerChannel = AcquirerChannelFactory.getAcquirerChannel(acquirer.getName());
            if (Objects.isNull(acquirerChannel)) {
                logger.log("No Matching AcquirerChannel for acquirer: " + acquirer.getName());
                // Move temp to failed before returning error
                PosFailedTransaction failedTxn = transactionAuxUtils.createFailedTransactionFromTemp(tempTransaction, null);
                failedTxn.setResponseMessage("No channel available");
                transactionAuxUtils.moveTempToFailed(tempTransaction.getId(), failedTxn);
                return generateErrorMsg(request);
            }

            IsoAcquirerTerminal isoAcquirerTerminal = new IsoAcquirerTerminal(acquirerTerminal.getTerminalId(),
                    acquirerTerminal.getAcquirerMerchant().getMerchantId(), acquirer.getNii(),
                    acquirer.getAcquirerInstitutionCode());

            Map<String, Object> metaData = manipulateMetaData(posTerminal);
            metaData.put(RouterConstants.ACQ_TERMINAL, acquirerTerminal);

            ISOMsg acqRequest = translator.toMessage(request, isoAcquirerTerminal, metaData);
            if (Objects.isNull(acqRequest)) {
                logger.log("No Request Generated For Jpos ");
                return generateErrorMsg(request);
            }

            // ===== ENHANCED BANK COMMUNICATION WITH TIMEOUT HANDLING =====
            
            // Send and receive the Payload with timeout monitoring
            ISOMsg acqResponse = null;
            try {
                // Start monitoring for response timeout
                long bankRequestStart = System.currentTimeMillis();
                
                acqResponse = acquirerChannel.transceive(acqRequest, acquirerConnection);
                
                long bankRequestDuration = System.currentTimeMillis() - bankRequestStart;
                logger.log("Bank response received in " + bankRequestDuration + "ms for terminal: " + bankTid);
                
            } catch (Exception communicationException) {
                logger.log("Communication error with acquirer: " + communicationException.getMessage());
                
                // Handle connection loss scenario
                reversalService.handleConnectionLoss(tempTransaction);
                
                // Return appropriate error
                return generateTimeoutErrorMsg(request);
            }
            
            if (Objects.isNull(acqResponse)) {
                logger.log("No Response From Acquirer " + acquirer.getName());
                
                // ===== RESPONSE TIMEOUT AUTO-REVERSAL =====
                
                // Trigger automatic reversal for timeout
                reversalService.handleResponseTimeout(tempTransaction);
                
                // Return timeout error to POS
                return generateTimeoutErrorMsg(request);
            }

            // Transform the Payload
            ISOMsg response = translator.fromMessage(acqResponse, request, metaData);

            // ===== ENHANCED SUCCESS/FAILURE HANDLING WITH DATABASE ERROR RECOVERY =====
            
            String responseCode = response.getString(39);
            try {
                if ("00".equals(responseCode) || "10".equals(responseCode) || "11".equals(responseCode)) {
                    // Success - move to success table with database error handling
                    PosTransaction successTxn = transactionAuxUtils.createSuccessTransactionFromTemp(tempTransaction, response);
                    transactionAuxUtils.moveTempToSuccess(tempTransaction.getId(), successTxn);
                    
                    logger.log("Transaction successful for terminal " + bankTid + ", response code: " + responseCode);
                    
                    // Record metrics
                    long totalDuration = System.currentTimeMillis() - startTime;
                    reversalMetrics.recordReversalCompleted("SUCCESSFUL_TRANSACTION", totalDuration);
                    
                } else {
                    // Failure - move to failed table with database error handling
                    PosFailedTransaction failedTxn = transactionAuxUtils.createFailedTransactionFromTemp(tempTransaction, response);
                    transactionAuxUtils.moveTempToFailed(tempTransaction.getId(), failedTxn);
                    
                    logger.log("Transaction failed for terminal " + bankTid + ", response code: " + responseCode);
                }
                
            } catch (Exception dbError) {
                logger.log("Database error during transaction completion: " + dbError.getMessage());
                
                // ===== DATABASE TRANSACTION ROLLBACK SCENARIO =====
                
                // Handle database error with reversal
                reversalService.handleDatabaseError(tempTransaction, dbError);
                
                // Return system error to POS
                ISOMsg errorResponse = (ISOMsg)request.clone(SUPPORTED_FIELDS);
                try {
                    errorResponse.setMTI(request.getMTI());
                    errorResponse.setResponseMTI();
                } catch (Exception e) {
                    // Ignore MTI setting errors
                }
                errorResponse.set(39, "96"); // System error
                return errorResponse;
            }

            // return the payload
            return response;*/
            
        } catch (Exception e) {
            logger.log("Exception while processing the txn: " + e.getMessage());
            logger.exceptionLog(e);

            transactionManager.cleanupTransaction();
            return generateErrorMsg(request);
        }
    }

    private ISOMsg generateErrorMsg(ISOMsg request) {
        ISOMsg response = (ISOMsg)request.clone(SUPPORTED_FIELDS);
        try {
            response.setMTI(request.getMTI());
            response.unset(2, 14, 35, 52, 55);
            response.setResponseMTI();
        } catch (Exception e) {
            // Ignore MTI setting errors
        }
        response.set(39, "96"); // Generic system error

        return response;
    }

    /**
     * Gracefully shutdown reversal services
     * This should be called when the application is shutting down
     */
    public void shutdown() {
        try {
            if (reversalService != null) {
                reversalService.shutdown();
            }
            if (reversalMetrics != null) {
                reversalMetrics.shutdown();
            }
            logger.log("TransactionRouter reversal services shutdown completed");
        } catch (Exception e) {
            logger.log("Error during reversal services shutdown: " + e.getMessage());
            logger.exceptionLog(e);
        }
    }
}
