package com.morefun.tester;

import org.jpos.iso.ISOMsg;
import org.jpos.iso.packager.ISO87BPackager;
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * Proper Mercury packet sender using jPOS for packet creation
 * Then adds BCD length and TPDU header for transmission
 */
public class MercuryJPOSPacket {
    
    // Transaction data from the specification
    private static final String PAN = "4854980600736740";
    private static final String PROCESSING_CODE = "0";
    private static final String AMOUNT = "6800";
    private static final String STAN = "75";
    private static final String TIME = "145824";
    private static final String DATE = "917";
    private static final String EXPIRY = "3105";
    private static final String POS_ENTRY_MODE = "51";
    private static final String CARD_SEQ = "0";
    private static final String FUNCTION_CODE = "782";
    private static final String POS_CONDITION = "0";
    private static final String TRACK2 = "4854980600736740=31052060011790";
    private static final String TERMINAL_ID = "12345671";
    private static final String MERCHANT_ID = "123456789012345";
    private static final String CURRENCY = "784";
    private static final String PIN_DATA = "7FFF886FC713BE1B";
    private static final String EMV_DATA = "9F2701809F100706011203A0A8029F37041F6157BD9F3602035B950500800400009A032509179C01009F02060000000000005F2A020356820238009F1A0203569F03060000000000009F3303E0E0C89F34034203009F3501229F1E0832333733303030318407A00000000310109F090200969F4104000003519F260847721533AAE8542E";
    private static final String BATCH_NUMBER = "000001";
    private static final String MAC = "203938";
    
    public static void main(String[] args) {
        MercuryJPOSPacket sender = new MercuryJPOSPacket();
        
        try {
            System.out.println("=== Mercury jPOS Packet Sender ===");
            System.out.println("Creating ISO8583 packet with jPOS, then adding transmission headers");
            System.out.println();
            
            // Create ISO8583 message using jPOS
            ISOMsg msg = sender.createMercuryMessage();
            
            // Pack the message using jPOS packager
            byte[] packedMsg = sender.packMessage(msg);
            
            // Add transmission headers (BCD length + TPDU)
            byte[] finalPacket = sender.addTransmissionHeaders(packedMsg);
            
            // Display packet details
            sender.displayPacketDetails(msg, packedMsg, finalPacket);
            
            // Send if network enabled
            if (NetworkConfig.ENABLE_NETWORK) {
                sender.sendPacket(finalPacket);
            } else {
                System.out.println("\n[INFO] Network disabled - set ENABLE_NETWORK = true to send");
            }
            
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * Create ISO8583 message using jPOS with the specified fields
     */
    private ISOMsg createMercuryMessage() throws Exception {
        System.out.println("Creating ISO8583 message with jPOS...");
        
        ISOMsg msg = new ISOMsg();
        msg.setPackager(new ISO87BPackager());
        
        // Set MTI
        msg.setMTI("0200");
        
        // Set all the specified fields
        msg.set(2, PAN);                          // Primary Account Number
        msg.set(3, PROCESSING_CODE);              // Processing Code
        msg.set(4, String.format("%012d", Long.parseLong(AMOUNT))); // Transaction Amount
        msg.set(11, String.format("%06d", Integer.parseInt(STAN))); // STAN
        msg.set(12, TIME);                        // Time
        msg.set(13, String.format("%04d", Integer.parseInt(DATE))); // Date
        msg.set(14, EXPIRY);                      // Expiration Date
        msg.set(22, String.format("%03d", Integer.parseInt(POS_ENTRY_MODE))); // POS Entry Mode
        msg.set(23, String.format("%03d", Integer.parseInt(CARD_SEQ))); // Card Sequence Number
        msg.set(24, String.format("%03d", Integer.parseInt(FUNCTION_CODE))); // Function Code
        msg.set(25, String.format("%02d", Integer.parseInt(POS_CONDITION))); // POS Condition Code
      //  msg.set(35, TRACK2);                      // Track 2 Data
        msg.set(41, TERMINAL_ID);                 // Terminal ID
        msg.set(42, MERCHANT_ID);                 // Merchant ID
        msg.set(49, CURRENCY);                    // Currency Code
        msg.set(52, hexStringToByteArray(PIN_DATA)); // PIN Data (binary)
        msg.set(55, hexStringToByteArray(EMV_DATA)); // EMV Data (binary)
        msg.set(62, BATCH_NUMBER);                // Batch Number
        
        // Skip field 64 (MAC) for now as it has specific binary format requirements
        // msg.set(64, MAC);  // Will add this once we confirm the format
        
        System.out.println("✓ ISO8583 message created with " + msg.getMaxField() + " fields");
        return msg;
    }
    
    /**
     * Pack the message using jPOS packager
     */
    private byte[] packMessage(ISOMsg msg) throws Exception {
        System.out.println("Packing message with jPOS packager...");
        
        byte[] packed = msg.pack();
        
        System.out.println("✓ Message packed: " + packed.length + " bytes");
        return packed;
    }
    
    /**
     * Add binary length prefix and TPDU header for transmission
     * Following PacketHead.java approach: first add TPDU, then add binary length
     */
    private byte[] addTransmissionHeaders(byte[] packedMsg) {
        System.out.println("Adding transmission headers...");
        
        // Step 1: Add TPDU header to message (following PacketHead.packHeadTpdu)
        byte[] tpduHeader = hexStringToByteArray(NetworkConfig.TPDU_HEADER);
        byte[] messageWithTpdu = new byte[tpduHeader.length + packedMsg.length];
        System.arraycopy(tpduHeader, 0, messageWithTpdu, 0, tpduHeader.length);
        System.arraycopy(packedMsg, 0, messageWithTpdu, tpduHeader.length, packedMsg.length);
        
        // Step 2: Add binary length prefix (2 bytes, big-endian)
        // This matches BytesUtils.intToBytes(request.length, LEN_BYTE_COUNT) behavior
        int totalLength = messageWithTpdu.length;
        byte[] binaryLength = new byte[2];
        binaryLength[0] = (byte) ((totalLength >> 8) & 0xFF);  // High byte
        binaryLength[1] = (byte) (totalLength & 0xFF);        // Low byte
        
        // Step 3: Combine: Binary Length + TPDU Header + Packed Message
        byte[] finalPacket = new byte[binaryLength.length + messageWithTpdu.length];
        System.arraycopy(binaryLength, 0, finalPacket, 0, binaryLength.length);
        System.arraycopy(messageWithTpdu, 0, finalPacket, binaryLength.length, messageWithTpdu.length);
        
        System.out.printf("✓ Transmission headers added: Binary length (%02X %02X = %d bytes) + TPDU (%d bytes) + Message (%d bytes) = %d total bytes%n",
                         binaryLength[0] & 0xFF, binaryLength[1] & 0xFF, totalLength,
                         tpduHeader.length, packedMsg.length, finalPacket.length);
        
        return finalPacket;
    }
    
    /**
     * Display detailed packet information
     */
    private void displayPacketDetails(ISOMsg msg, byte[] packedMsg, byte[] finalPacket) throws Exception {
        System.out.println("\n=== Packet Details ===");
        
        // Show message fields
        System.out.println("ISO8583 Fields:");
        System.out.println("  MTI: " + msg.getMTI());
        System.out.println("  Field 2 (PAN): " + maskPAN(msg.getString(2)));
        System.out.println("  Field 3 (Proc Code): " + msg.getString(3));
        System.out.println("  Field 4 (Amount): " + msg.getString(4) + " (" + formatAmount(msg.getString(4)) + ")");
        System.out.println("  Field 11 (STAN): " + msg.getString(11));
        System.out.println("  Field 12 (Time): " + msg.getString(12));
        System.out.println("  Field 13 (Date): " + msg.getString(13));
        System.out.println("  Field 41 (Terminal): " + msg.getString(41));
        System.out.println("  Field 42 (Merchant): " + msg.getString(42));
        System.out.println("  Field 49 (Currency): " + msg.getString(49));
        
    
        // Show sizes
        System.out.println("\nPacket Sizes:");
        System.out.println("  jPOS packed message: " + packedMsg.length + " bytes");
        System.out.println("  Final packet (with headers): " + finalPacket.length + " bytes");
        
        // Show transmission format
        System.out.println("\nTransmission Format:");
        int binaryLength = ((finalPacket[0] & 0xFF) << 8) | (finalPacket[1] & 0xFF);
        System.out.printf("  Binary Length: %02X %02X (%d decimal)%n", 
                         finalPacket[0] & 0xFF, finalPacket[1] & 0xFF, binaryLength);
        
        System.out.print("  TPDU Header: ");
        for (int i = 2; i < 7 && i < finalPacket.length; i++) {
            System.out.printf("%02X ", finalPacket[i] & 0xFF);
        }
        System.out.println("(" + NetworkConfig.TPDU_HEADER + ")");
        
        // Show hex dump
        if (NetworkConfig.SHOW_RAW_MESSAGES) {
            System.out.println("\nFull Packet Hex Dump:");
            String hexString = byteArrayToHex(finalPacket);
            
            // Format in lines of 32 hex characters (16 bytes)
            StringBuilder formatted = new StringBuilder();
            for (int i = 0; i < hexString.length(); i += 32) {
                String line = hexString.substring(i, Math.min(i + 32, hexString.length()));
                formatted.append(String.format("%04X: ", i/2));
                
                // Add spaces between bytes
                for (int j = 0; j < line.length(); j += 2) {
                    formatted.append(line.substring(j, Math.min(j + 2, line.length()))).append(" ");
                }
                formatted.append("\n");
            }
            System.out.println(formatted.toString());
        }
    }
    
    /**
     * Send the packet via TCP
     */
    private void sendPacket(byte[] packet) {
        System.out.println("\n=== Sending Packet ===");
        System.out.printf("Target: %s:%d%n", NetworkConfig.SERVER_IP, NetworkConfig.SERVER_PORT);
        
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(NetworkConfig.SERVER_IP, NetworkConfig.SERVER_PORT), 
                          NetworkConfig.CONNECTION_TIMEOUT);
            socket.setSoTimeout(NetworkConfig.READ_TIMEOUT);
            
            System.out.println("✓ Connected to server");
            
            // Send the complete packet (BCD length + TPDU + jPOS message)
            OutputStream out = socket.getOutputStream();
            out.write(packet);
            out.flush();
            
            System.out.println("✓ Packet sent (" + packet.length + " bytes)");
            
            // Read response
            InputStream in = socket.getInputStream();
            byte[] buffer = new byte[2048];
            int bytesRead = in.read(buffer);
            
            if (bytesRead > 0) {
                byte[] response = new byte[bytesRead];
                System.arraycopy(buffer, 0, response, 0, bytesRead);
                
                System.out.println("✓ Response received (" + bytesRead + " bytes)");
                
                // Display response
                displayResponse(response);
                
            } else {
                System.out.println("No response received");
            }
            
        } catch (Exception e) {
            System.err.println("Network error: " + e.getMessage());
            
            if (e instanceof ConnectException) {
                System.err.println("Connection refused - check server availability");
            } else if (e instanceof SocketTimeoutException) {
                System.err.println("Timeout - server may be slow or unresponsive");
            }
        }
    }
    
    /**
     * Display response packet details
     */
    private void displayResponse(byte[] response) {
        System.out.println("\n=== Response Analysis ===");
        
        if (response.length >= 2) {
            int responseLength = ((response[0] & 0xFF) * 100) + (response[1] & 0xFF);
            System.out.printf("Response BCD Length: %02X %02X (%d)%n", 
                             response[0] & 0xFF, response[1] & 0xFF, responseLength);
        }
        
        if (response.length >= 9) {
            System.out.printf("Response MTI: %02X%02X%n", 
                             response[7] & 0xFF, response[8] & 0xFF);
        }
        
        System.out.println("Response Hex: " + byteArrayToHex(response));
        
        // Try to parse with jPOS if it looks like ISO8583
        try {
            if (response.length > 7) {
                // Extract message part (skip BCD length + TPDU)
                byte[] msgPart = new byte[response.length - 7];
                System.arraycopy(response, 7, msgPart, 0, msgPart.length);
                
                ISOMsg responseMsg = new ISOMsg();
                responseMsg.setPackager(new ISO87BPackager());
                responseMsg.unpack(msgPart);
                
                System.out.println("✓ Response parsed successfully");
                System.out.println("Response MTI: " + responseMsg.getMTI());
                
                if (responseMsg.hasField(39)) {
                    String responseCode = responseMsg.getString(39);
                    System.out.println("Response Code: " + responseCode);
                    System.out.println("Status: " + ("00".equals(responseCode) ? "APPROVED" : "DECLINED"));
                }
                
                if (responseMsg.hasField(38)) {
                    System.out.println("Auth Code: " + responseMsg.getString(38));
                }
                
            }
        } catch (Exception e) {
            System.out.println("Could not parse response as ISO8583: " + e.getMessage());
        }
    }
    
    // Utility methods
    private byte[] hexStringToByteArray(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                                 + Character.digit(hex.charAt(i+1), 16));
        }
        return data;
    }
    
    private String byteArrayToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02X", b & 0xFF));
        }
        return result.toString();
    }
    
    private String maskPAN(String pan) {
        if (pan == null || pan.length() < 8) return pan;
        return pan.substring(0, 4) + "****" + pan.substring(pan.length() - 4);
    }
    
    private String formatAmount(String amount) {
        if (amount == null) return "0.00";
        try {
            long cents = Long.parseLong(amount);
            return String.format("%.2f", cents / 100.0);
        } catch (Exception e) {
            return amount;
        }
    }
}