package com.morefun.tester;

import java.io.*;
import java.net.*;
import java.util.HashMap;
import java.util.Map;

/**
 * Mercury-specific ISO8583 packet builder
 * Creates the exact packet format requested with TPDU header and BCD length
 */
public class MercuryPacketBuilder {
    
    // TPDU Header for Mercury packets
    private static final String TPDU_HEADER = "6000782000";
    
    public static void main(String[] args) {
        MercuryPacketBuilder builder = new MercuryPacketBuilder();
        
        try {
            System.out.println("=== Mercury ISO8583 Packet Builder ===");
            System.out.println("Building specific packet with TPDU header and BCD length encoding");
            System.out.println();
            
            // Create the specified packet
            byte[] packet = builder.createMercuryPacket();
            
            // Display packet details
            builder.displayPacketDetails(packet);
            
            // Send packet if network is enabled
            if (NetworkConfig.ENABLE_NETWORK) {
                builder.sendPacket(packet);
            } else {
                System.out.println("\n[INFO] Network disabled - packet created but not sent");
                System.out.println("Set ENABLE_NETWORK = true in NetworkConfig to actually send");
            }
            
        } catch (Exception e) {
            System.err.println("Error creating Mercury packet: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    /**
     * Creates the specific Mercury ISO8583 packet as requested
     */
    public byte[] createMercuryPacket() throws Exception {
        System.out.println("Building Mercury ISO8583 packet...");
        
        // Build the ISO8583 message fields
        Map<Integer, String> fields = new HashMap<>();
        
        // Set the exact fields as specified
        fields.put(0, "0200");                    // MTI
        fields.put(2, "4854980600736740");        // PAN
        fields.put(3, "0");                       // Processing code
        fields.put(4, "6800");                    // Transaction amount
        fields.put(11, "75");                     // STAN
        fields.put(12, "145824");                 // Time
        fields.put(13, "917");                    // Date
        fields.put(14, "3105");                   // Expiration date
        fields.put(22, "51");                     // POS entry mode
        fields.put(23, "0");                      // Card sequence number
        fields.put(24, "782");                    // Function code
        fields.put(25, "0");                      // POS condition code
        fields.put(35, "4854980600736740=31052060011790");  // Track 2 data
        fields.put(41, "12345671");               // Terminal ID
        fields.put(42, "123456789012345");        // Merchant ID
        fields.put(49, "784");                    // Currency code
        fields.put(62, "000001");                 // Batch number
        fields.put(64, "203938");                 // MAC
        
        // Binary fields (hex strings)
        Map<Integer, String> binaryFields = new HashMap<>();
        binaryFields.put(52, "7FFF886FC713BE1B"); // PIN data
        binaryFields.put(55, "9F2701809F100706011203A0A8029F37041F6157BD9F3602035B950500800400009A032509179C01009F02060000000000005F2A020356820238009F1A0203569F03060000000000009F3303E0E0C89F34034203009F3501229F1E0832333733303030318407A00000000310109F090200969F4104000003519F260847721533AAE8542E");
        
        // Build the packed message
        String packedMessage = packMercuryMessage(fields, binaryFields);
        
        // Add TPDU header
        String messageWithTpdu = TPDU_HEADER + packedMessage;
        
        // Calculate length and create BCD length prefix
        int messageLength = messageWithTpdu.length() / 2; // Convert hex chars to bytes
        byte[] bcdLength = convertToBcdLength(messageLength);
        
        // Convert hex string to bytes
        byte[] messageBytes = hexStringToByteArray(messageWithTpdu);
        
        // Combine BCD length + message
        byte[] fullPacket = new byte[bcdLength.length + messageBytes.length];
        System.arraycopy(bcdLength, 0, fullPacket, 0, bcdLength.length);
        System.arraycopy(messageBytes, 0, fullPacket, bcdLength.length, messageBytes.length);
        
        System.out.println("✓ Mercury packet created successfully");
        return fullPacket;
    }
    
    /**
     * Pack the Mercury message in the specific format
     */
    private String packMercuryMessage(Map<Integer, String> fields, Map<Integer, String> binaryFields) {
        StringBuilder packed = new StringBuilder();
        
        // Start with MTI
        packed.append("0400");  // Response MTI for the message type
        
        // Create bitmap (simplified - in real implementation this would be calculated)
        // Based on the fields present, create appropriate bitmap
        String bitmap = "703C078000C08204"; // This matches the example pattern
        packed.append(bitmap);
        
        // Add fields in order (simplified packing)
        if (fields.containsKey(2)) {
            String pan = fields.get(2);
            packed.append(String.format("%02d", pan.length())).append(pan);
        }
        
        if (fields.containsKey(3)) {
            packed.append(String.format("%06d", Integer.parseInt(fields.get(3))));
        }
        
        if (fields.containsKey(4)) {
            packed.append(String.format("%012d", Integer.parseInt(fields.get(4))));
        }
        
        if (fields.containsKey(11)) {
            packed.append(String.format("%06d", Integer.parseInt(fields.get(11))));
        }
        
        if (fields.containsKey(12)) {
            packed.append(fields.get(12));
        }
        
        if (fields.containsKey(13)) {
            packed.append(String.format("%04d", Integer.parseInt(fields.get(13))));
        }
        
        if (fields.containsKey(14)) {
            packed.append(fields.get(14));
        }
        
        if (fields.containsKey(22)) {
            packed.append(String.format("%03d", Integer.parseInt(fields.get(22))));
        }
        
        if (fields.containsKey(23)) {
            packed.append(String.format("%03d", Integer.parseInt(fields.get(23))));
        }
        
        if (fields.containsKey(24)) {
            packed.append(String.format("%03d", Integer.parseInt(fields.get(24))));
        }
        
        if (fields.containsKey(25)) {
            packed.append(String.format("%02d", Integer.parseInt(fields.get(25))));
        }
        
        if (fields.containsKey(35)) {
            String track2 = fields.get(35);
            packed.append(String.format("%02d", track2.length())).append(track2);
        }
        
        if (fields.containsKey(41)) {
            packed.append(fields.get(41));
        }
        
        if (fields.containsKey(42)) {
            packed.append(fields.get(42));
        }
        
        if (fields.containsKey(49)) {
            packed.append(fields.get(49));
        }
        
        // Add binary fields
        if (binaryFields.containsKey(55)) {
            String emvData = binaryFields.get(55);
            packed.append(String.format("%03d", emvData.length() / 2)).append(emvData);
        }
        
        if (fields.containsKey(62)) {
            String batchNumber = fields.get(62);
            packed.append(String.format("%03d", batchNumber.length())).append(batchNumber);
        }
        
        if (fields.containsKey(64)) {
            packed.append(fields.get(64));
        }
        
        return packed.toString().toUpperCase();
    }
    
    /**
     * Convert length to BCD format (2 bytes)
     * Example: length 111 becomes 01 11 in hex
     */
    private byte[] convertToBcdLength(int length) {
        // Convert to BCD format
        String lengthStr = String.format("%04d", length);
        
        // Convert each pair of digits to BCD
        byte[] bcdBytes = new byte[2];
        bcdBytes[0] = (byte) Integer.parseInt(lengthStr.substring(0, 2), 16);
        bcdBytes[1] = (byte) Integer.parseInt(lengthStr.substring(2, 4), 16);
        
        return bcdBytes;
    }
    
    /**
     * Convert hex string to byte array
     */
    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;
    }
    
    /**
     * Convert byte array to hex string for display
     */
    private String byteArrayToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02X", b & 0xFF));
        }
        return result.toString();
    }
    
    /**
     * Display packet details
     */
    private void displayPacketDetails(byte[] packet) {
        System.out.println("\n=== Mercury Packet Details ===");
        System.out.println("Total packet length: " + packet.length + " bytes");
        
        // Show BCD length (first 2 bytes)
        System.out.printf("BCD Length: %02X %02X%n", packet[0] & 0xFF, packet[1] & 0xFF);
        
        // Show TPDU header (next 5 bytes after length)
        System.out.print("TPDU Header: ");
        for (int i = 2; i < 7 && i < packet.length; i++) {
            System.out.printf("%02X ", packet[i] & 0xFF);
        }
        System.out.println();
        
        // Show full packet in hex
        System.out.println("\nFull packet (hex):");
        String hexString = byteArrayToHex(packet);
        
        // Format hex string with spaces for readability
        StringBuilder formatted = new StringBuilder();
        for (int i = 0; i < hexString.length(); i += 2) {
            if (i > 0 && i % 32 == 0) {
                formatted.append("\n");
            }
            formatted.append(hexString.substring(i, Math.min(i + 2, hexString.length()))).append(" ");
        }
        System.out.println(formatted.toString());
        
        // Compare with expected example
        String expectedStart = "011460007820000400703C078000C08204164854980600736740";
        String actualStart = hexString.substring(0, Math.min(expectedStart.length(), hexString.length()));
        
        System.out.println("\n=== Comparison with Expected ===");
        System.out.println("Expected start: " + expectedStart);
        System.out.println("Actual start:   " + actualStart);
        System.out.println("Match: " + (actualStart.equalsIgnoreCase(expectedStart) ? "YES" : "PARTIAL"));
    }
    
    /**
     * Send the packet via TCP
     */
    private void sendPacket(byte[] packet) {
        System.out.println("\n=== Sending Mercury Packet ===");
        System.out.println("Target: " + 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);
            
            // Send packet
            OutputStream out = socket.getOutputStream();
            out.write(packet);
            out.flush();
            
            System.out.println("✓ Packet sent successfully (" + packet.length + " bytes)");
            
            // Try to read response
            InputStream in = socket.getInputStream();
            byte[] responseBuffer = new byte[1024];
            int bytesRead = in.read(responseBuffer);
            
            if (bytesRead > 0) {
                byte[] response = new byte[bytesRead];
                System.arraycopy(responseBuffer, 0, response, 0, bytesRead);
                
                System.out.println("✓ Response received (" + bytesRead + " bytes)");
                System.out.println("Response: " + byteArrayToHex(response));
            } else {
                System.out.println("No response received");
            }
            
            socket.close();
            System.out.println("✓ Connection closed");
            
        } catch (Exception e) {
            System.err.println("Error sending packet: " + e.getMessage());
            e.printStackTrace();
        }
    }
}