package org.jpos.tcpay.service;

import com.google.gson.Gson;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.packager.ISO87BPackager;
import org.jpos.tcpay.ReversalTransactionManagerImpl;
import org.jpos.tcpay.connection.AcquirerChannel;
import org.jpos.tcpay.connection.AcquirerChannelFactory;
import org.jpos.tcpay.data_element.AdditionalData;
import org.jpos.tcpay.db.entity.*;
import org.jpos.tcpay.model.IsoAcquirerTerminal;
import org.jpos.util.AppLogger;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Service for handling transaction reversal scenarios
 * Implements the comprehensive reversal management system
 */
public class ReversalService {
    Gson gson = new Gson();
    private final AppLogger logger = new AppLogger();
    private final TransactionAuxUtils transactionAuxUtils;
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    
    // Configuration parameters
    private static final int STALE_TRANSACTION_THRESHOLD_SECONDS = 45;
    private static final int RESPONSE_TIMEOUT_SECONDS = 30;
    private static final int MAX_RETRY_ATTEMPTS = 3;
    private static final int RETRY_DELAY_SECONDS = 60;
    private static final int CONNECTION_RECOVERY_TIMEOUT_SECONDS = 30;
    private static final int STARTUP_CLEANUP_AGE_THRESHOLD_MINUTES = 5;
    private static final int MAX_PENDING_REVERSALS = 10;
    private static final int MAX_TEMP_AGE_MINUTES = 10;
    
    public ReversalService(TransactionAuxUtils transactionAuxUtils) {
        this.transactionAuxUtils = transactionAuxUtils;
        // Initialize the service and start cleanup
        // initialize();
    }
    
    /**
     * Initialize the reversal service
     */
    private void initialize() {
        try {
            // Start health checks
            // startHealthChecks();
            
            // Perform startup cleanup after a short delay to allow system to stabilize
            //scheduler.schedule(this::cleanupOrphanedTransactions, 30, TimeUnit.SECONDS);
            
            logger.log("ReversalService initialized successfully");
        } catch (Exception e) {
            logger.log("Error initializing ReversalService: " + e.getMessage());
            logger.exceptionLog(e);
        }
    }
    
    // =================== REVERSAL SCENARIO IMPLEMENTATIONS ===================
    
    /**
     * Scenario 1: Duplicate Request Detection and Stale Transaction Cleanup
     * Check and handle stale transactions before processing new requests
     */
    public boolean checkAndHandleStaleTransactions(String bankTid, String bankMid) {
        try {
            PosTempTransaction existingTemp = transactionAuxUtils.findTempByBankTidMid(bankTid, bankMid);
            
            if (existingTemp != null) {
                long ageInSeconds = java.time.Duration.between(
                    existingTemp.getCreatedDateTime(), 
                    LocalDateTime.now()
                ).getSeconds();
                
                if (ageInSeconds > STALE_TRANSACTION_THRESHOLD_SECONDS) {
                    logger.log("Found stale temp transaction (age: " + ageInSeconds + "s) for terminal: " + bankTid);
                    
                    // Check if reversal already exists
                    if (!transactionAuxUtils.reversalExists(existingTemp)) {
                        // Initiate reversal before processing new transaction
                        initiateReversal(existingTemp, PosTransactionReversal.REASON_STALE_TRANSACTION);
                        return false; // Block new transaction until reversal completes
                    }
                }
            }
            return true; // Safe to proceed
            
        } catch (Exception e) {
            logger.log("Error checking stale transactions: " + e.getMessage());
            logger.exceptionLog(e);
            return false; // Block new transaction on error
        }
    }

    /**
     * Scenario 2: Response Timeout Auto-Reversal
     * Handle bank response timeout with automatic reversal
     */
    public void handleResponseTimeout(PosTempTransaction tempTransaction) {
        try {
            // Update temp transaction status to indicate reversal in progress
            transactionAuxUtils.updateTempTransactionStatus(
                tempTransaction.getId(),
                PosTempTransaction.TempTransactionStatus.RESPONSE_TIMEOUT
            );

            // Initiate auto-reversal with bank
            PosTransactionReversal reversal = transactionAuxUtils.createReversalRequest(
                tempTransaction,
                PosTransactionReversal.REASON_RESPONSE_TIMEOUT
            );

            // Send reversal to bank (MTI 0400/0420)
            ISOMsg reversalResponse = sendReversalToBank(reversal);

            if (reversalResponse != null && isReversalSuccessful(reversalResponse)) {
                // Successful reversal - clean temp and record
                transactionAuxUtils.atomicTempCleanupWithReversalRecord(
                    tempTransaction, reversal, reversalResponse
                );
                logger.log("Auto-reversal successful for timeout on terminal: " + tempTransaction.getBTid());
            } else {
                // Reversal failed - mark for retry and keep in temp
                transactionAuxUtils.updateTempTransactionStatus(
                    tempTransaction.getId(),
                    PosTempTransaction.TempTransactionStatus.REVERSAL_FAILED
                );
                scheduleReversalRetry(tempTransaction, reversal);
            }

        } catch (Exception e) {
            logger.log("Auto-reversal failed for transaction: " + tempTransaction.getId());
            logger.exceptionLog(e);
            transactionAuxUtils.updateTempTransactionStatus(
                tempTransaction.getId(),
                PosTempTransaction.TempTransactionStatus.REVERSAL_ERROR
            );
        }
    }

    /**
     * Scenario 3: Connection Loss During Transaction
     * Handle TCP/IP connection loss during transaction processing
     */
    public void handleConnectionLoss(PosTempTransaction tempTransaction) {
        try {
            // Mark transaction as connection lost
            transactionAuxUtils.updateTempTransactionStatus(
                tempTransaction.getId(),
                PosTempTransaction.TempTransactionStatus.CONNECTION_LOST
            );

            // Wait for connection recovery with timeout
            if (waitForConnectionRecovery(CONNECTION_RECOVERY_TIMEOUT_SECONDS * 1000)) {
                // Connection recovered - attempt reversal
                initiateReversal(tempTransaction, PosTransactionReversal.REASON_CONNECTION_LOST);
            } else {
                // Connection still down - mark for later reversal
                transactionAuxUtils.updateTempTransactionStatus(
                    tempTransaction.getId(),
                    PosTempTransaction.TempTransactionStatus.PENDING_MANUAL_REVIEW
                );
                logger.log("Connection recovery timeout - temp transaction marked for manual review: " +
                          tempTransaction.getId());
            }

        } catch (Exception e) {
            logger.log("Error handling connection loss: " + e.getMessage());
            logger.exceptionLog(e);
        }
    }
    
    /**
     * System Startup Cleanup
     * Clean up orphaned temp transactions on system restart
     */
    public void cleanupOrphanedTransactions() {
        try {
            logger.log("Starting system startup cleanup of orphaned temp transactions");
            
            List<PosTempTransaction> orphanedTemps = transactionAuxUtils.findStaleTemps(
                STARTUP_CLEANUP_AGE_THRESHOLD_MINUTES
            );
            
            int cleanedUp = 0;
            for (PosTempTransaction tempTxn : orphanedTemps) {
                try {
                    // Check if reversal already exists
                    if (!transactionAuxUtils.reversalExists(tempTxn)) {
                        // Initiate reversal for orphaned transaction
                        initiateReversal(tempTxn, PosTransactionReversal.REASON_SYSTEM_STARTUP);
                        cleanedUp++;
                    }
                } catch (Exception e) {
                    logger.log("Error during startup cleanup of temp txn " + tempTxn.getId() + ": " + e.getMessage());
                }
            }
            
            logger.log("System startup cleanup completed: " + cleanedUp + " orphaned transactions processed");
            
        } catch (Exception e) {
            logger.log("Error during system startup cleanup: " + e.getMessage());
            logger.exceptionLog(e);
        }
    }
    
    /**
     * Scenario 5: Database Transaction Rollback
     * Handle database errors during temp to final table movement
     */
    public void handleDatabaseError(PosTempTransaction tempTransaction, Exception dbError) {
        try {
            logger.log("Handling database error for temp transaction: " + tempTransaction.getId());
            
            // If we can't complete the transaction locally, we must reverse
            initiateReversal(tempTransaction, PosTransactionReversal.REASON_DATABASE_ERROR);
            
        } catch (Exception reversalError) {
            // Critical: Log for manual intervention
            logger.log("CRITICAL: Unable to initiate reversal after DB error for temp txn: " + 
                      tempTransaction.getId());
            logger.exceptionLog(reversalError);
            alertOperations(tempTransaction, dbError, reversalError);
        }
    }
    
    // =================== HELPER METHODS ===================
    
    /**
     * Initiate reversal for a temp transaction
     */
    private void initiateReversal(PosTempTransaction tempTransaction, String reason) {
        try {
            // Update temp transaction status
            transactionAuxUtils.updateTempTransactionStatus(
                tempTransaction.getId(), 
                PosTempTransaction.TempTransactionStatus.REVERSAL_PENDING
            );
            
            // Create reversal request
            PosTransactionReversal reversal = transactionAuxUtils.createReversalRequest(tempTransaction, reason);
            reversal = transactionAuxUtils.saveReversalRecord(reversal);
            
            // Make variables effectively final for lambda
            final PosTransactionReversal finalReversal = reversal;
            final PosTempTransaction finalTempTransaction = tempTransaction;
            
            // Send reversal asynchronously to avoid blocking
            CompletableFuture.runAsync(() -> {
                try {
                    ISOMsg reversalResponse = sendReversalToBank(finalReversal);
                    
                    if (reversalResponse != null && isReversalSuccessful(reversalResponse)) {
                        // Successful reversal
                        transactionAuxUtils.atomicTempCleanupWithReversalRecord(
                            finalTempTransaction, finalReversal, reversalResponse
                        );
                        logger.log("Reversal successful for temp txn: " + finalTempTransaction.getId());
                    } else {
                        // Failed reversal - schedule retry
                        scheduleReversalRetry(finalTempTransaction, finalReversal);
                    }
                    
                } catch (Exception e) {
                    logger.log("Error in async reversal processing: " + e.getMessage());
                    logger.exceptionLog(e);
                    scheduleReversalRetry(finalTempTransaction, finalReversal);
                }
            });
            
        } catch (Exception e) {
            logger.log("Error initiating reversal: " + e.getMessage());
            logger.exceptionLog(e);
            throw new RuntimeException("Failed to initiate reversal", e);
        }
    }
    
    /**
     * Send reversal message to bank
     */
    private ISOMsg sendReversalToBank(PosTransactionReversal reversal) {
        try {

            AcquirerConnection acquirerConnection = transactionAuxUtils.getAcquirerConnectionByAcquirerId(reversal.getAcquirerId());
            IsoAcquirerTerminal isoAcquirerTerminal =
                    new IsoAcquirerTerminal(reversal.getBTid(), reversal.getBMid(),
                            acquirerConnection.getAcquirer().getNii(), acquirerConnection.getAcquirer().getAcquirerInstitutionCode());
            // Build reversal ISO message (MTI 0400/0420)
            reversal.setInitiatedDateTime(LocalDateTime.now());
            ISOMsg reversalMsg = buildReversalMessage(reversal, isoAcquirerTerminal);

            // Update reversal record with request
            reversal.setReversalRequest(reversalMsg.toString());
            reversal.setReversalStatus(PosTransactionReversal.ReversalStatus.SENT);
            transactionAuxUtils.saveReversalRecord(reversal);

            ReversalTransactionManagerImpl reversalTransactionManager = new ReversalTransactionManagerImpl(transactionAuxUtils, null, this);
            reversalTransactionManager.init(acquirerConnection);

            boolean result = reversalTransactionManager.checkAndValidateRequest(reversalMsg);
            if(!result) {
                logger.log("Request validation failed");
                return generateErrorMsg(reversalMsg);
            }
            reversalTransactionManager.onboardRequest(reversalMsg);
            ISOMsg response = reversalTransactionManager.processTransaction(reversalMsg);
            return response;
            
        } catch (Exception e) {
            logger.log("Error sending reversal to bank: " + e.getMessage());
            logger.exceptionLog(e);
            return null;
        }
    }
    
    /**
     * Build ISO reversal message
     */
    private ISOMsg buildReversalMessage(PosTransactionReversal reversal, IsoAcquirerTerminal isoAcquirerTerminal) throws Exception {
        ISOMsg reversalMsg = new ISOMsg();
        reversalMsg.setPackager(new ISO87BPackager());

        AdditionalData additionalData = new AdditionalData();
        additionalData.setOriginalMti(reversal.getOriginalMti()).setOriginalDate(reversal.getOriginalDate())
                        .setOriginalTime(reversal.getOriginalTime()).setOriginalStan(reversal.getOriginalStan());
        reversalMsg.set(47, gson.toJson(additionalData));

        reversalMsg.setMTI(reversal.getReversalMti());
        reversalMsg.set(2, reversal.getOriginalEncryptedPan());
        reversalMsg.set(14, reversal.getOriginalEncryptedExpiryDate());

        // Set required fields for reversal
        if (reversal.getOriginalProcCode() != null) {
            reversalMsg.set(3, reversal.getOriginalProcCode());
        }

        if (reversal.getOriginalAmount() != null) {
            System.out.println("Amount " + reversal.getOriginalAmount().movePointRight(reversal.getOriginalAmount().scale()));
            reversalMsg.set(4, ""+reversal.getOriginalAmount().movePointRight(reversal.getOriginalAmount().scale()).intValueExact());
        }
        reversalMsg.set(11, reversal.getBTidStan());
        reversalMsg.set(12, reversal.getInitiatedDateTime().format(java.time.format.DateTimeFormatter.ofPattern("HHmmss")));
        reversalMsg.set(13, reversal.getInitiatedDateTime().format(java.time.format.DateTimeFormatter.ofPattern("MMdd")));
        reversalMsg.set(19, isoAcquirerTerminal.getAcquiringInstCode());
        reversalMsg.set(22, reversal.getOriginalEntryMode());

        // Set PAN Seq
        if (reversal.getOriginalPanSequence() != null) {
            reversalMsg.set(23, reversal.getOriginalPanSequence());
        }
        reversalMsg.set(24, isoAcquirerTerminal.getNii());

        reversalMsg.set(37, reversal.getOriginalReferenceNo());
        reversalMsg.set(41, reversal.getBTid());
        reversalMsg.set(42, reversal.getBMid());
        reversalMsg.set(49, reversal.getOriginalCurrencyCode());
        reversalMsg.set(62, reversal.getOriginalBatchNo());
        reversalMsg.set(63, "JPOS_REVERSAL");

        return reversalMsg;
    }
    
    /**
     * Check if reversal response indicates success
     */
    private boolean isReversalSuccessful(ISOMsg reversalResponse) {
        try {
            String responseCode = reversalResponse.getString(39);
            return "00".equals(responseCode) || "21".equals(responseCode);
        } catch (Exception e) {
            return false;
        }
    }
    
    /**
     * Schedule reversal retry
     */
    private void scheduleReversalRetry(PosTempTransaction tempTransaction, PosTransactionReversal reversal) {
        try {
            reversal.setRetryCount(reversal.getRetryCount() + 1);
            
            if (reversal.getRetryCount() >= MAX_RETRY_ATTEMPTS) {
                reversal.setReversalStatus(PosTransactionReversal.ReversalStatus.MAX_RETRIES_EXCEEDED);
                transactionAuxUtils.updateTempTransactionStatus(
                    tempTransaction.getId(), 
                    PosTempTransaction.TempTransactionStatus.PENDING_MANUAL_REVIEW
                );
                logger.log("Max retry attempts exceeded for reversal of temp txn: " + tempTransaction.getId());
            } else {
                reversal.setReversalStatus(PosTransactionReversal.ReversalStatus.RETRY_SCHEDULED);
                reversal.setNextRetryTime(LocalDateTime.now().plusSeconds(RETRY_DELAY_SECONDS));
                
                // Schedule retry
                scheduler.schedule(() -> {
                    try {
                        ISOMsg response = sendReversalToBank(reversal);
                        if (response != null && isReversalSuccessful(response)) {
                            transactionAuxUtils.atomicTempCleanupWithReversalRecord(
                                tempTransaction, reversal, response
                            );
                        } else {
                            scheduleReversalRetry(tempTransaction, reversal);
                        }
                    } catch (Exception e) {
                        logger.log("Error in scheduled reversal retry: " + e.getMessage());
                        scheduleReversalRetry(tempTransaction, reversal);
                    }
                }, RETRY_DELAY_SECONDS, TimeUnit.SECONDS);
            }
            
            transactionAuxUtils.saveReversalRecord(reversal);
            
        } catch (Exception e) {
            logger.log("Error scheduling reversal retry: " + e.getMessage());
            logger.exceptionLog(e);
        }
    }
    
    /**
     * Wait for connection recovery (simplified implementation)
     */
    private boolean waitForConnectionRecovery(long timeoutMs) {
        try {
            // This would implement actual connection health check
            Thread.sleep(Math.min(timeoutMs, 5000)); // Max 5 second wait
            return true; // Assume recovery for now
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    /**
     * Alert operations team for manual intervention
     */
    private void alertOperations(PosTempTransaction tempTransaction, Exception dbError, Exception reversalError) {
        logger.log("ALERT: Manual intervention required for temp transaction: " + tempTransaction.getId());
        logger.log("Database error: " + dbError.getMessage());
        logger.log("Reversal error: " + reversalError.getMessage());
        
        // In production, this would send alerts via email, SMS, monitoring system, etc.
        // For now, just log the critical situation
    }
    
    /**
     * Health check for reversal system
     */
    public void performReversalHealthCheck() {
        try {
            int pendingReversals = transactionAuxUtils.countPendingReversals();
            int staleTemps = transactionAuxUtils.countStaleTemps(MAX_TEMP_AGE_MINUTES);
            
            if (pendingReversals > MAX_PENDING_REVERSALS || staleTemps > 0) {
                logger.log("Reversal system health check FAILED - Pending: " + pendingReversals + 
                          ", Stale: " + staleTemps);
                // Alert operations
            } else {
                logger.log("Reversal system health check PASSED");
            }
            
        } catch (Exception e) {
            logger.log("Error in reversal health check: " + e.getMessage());
            logger.exceptionLog(e);
        }
    }
    
    /**
     * Start periodic health checks
     */
    public void startHealthChecks() {
        scheduler.scheduleAtFixedRate(
            this::performReversalHealthCheck, 
            60, 60, TimeUnit.SECONDS // Every minute
        );
    }
    
    /**
     * Shutdown scheduler
     */
    public void shutdown() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
        }
    }

    public ISOMsg generateErrorMsg(ISOMsg request) {
        try {
            ISOMsg errorResponse = (ISOMsg)request.clone();
            errorResponse.setMTI(request.getMTI());
            errorResponse.setResponseMTI();
            errorResponse.set(39, "06"); // Error
            return errorResponse;
        } catch (Exception e) {
            logger.log("Error creating error response: " + e.getMessage());
            return null;
        }
    }
}