package org.example.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.apache.tomcat.util.buf.HexUtils;
import org.example.jpos.ISO87DPackager;
import org.example.utils.YcsUtils;
import org.example.ycs.MessageResponderMatcher;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.packager.ISO87BPackager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

@ChannelHandler.Sharable
@Component
@Log4j2
public class TcpServerHandler extends ChannelInboundHandlerAdapter {

    @Autowired
    MessageResponderMatcher messageResponderMatcher;

    private final int ADD_PACKET_LENGTH = 9; // 14

    private final boolean isCRCEnabled = false;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("Handler Added ");
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("Handler Removed ");
        super.handlerRemoved(ctx);

    }
    @SneakyThrows
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf byteBuf = (ByteBuf) msg;
        byte[] rawRequest = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(rawRequest);
        log.info("Raw Received: " + HexUtils.toHexString(rawRequest));
        //msg.getBytes(StandardCharsets.UTF_8);
        int isoPayloadLength = rawRequest.length - ADD_PACKET_LENGTH; // 14
        if (isCRCEnabled) {
            isoPayloadLength -= 2;
        }

        byte[] crc16Req = new byte[2];
        byte[] isoPayLoad = new byte[isoPayloadLength];
        byte[] ADD_ON_PAYLOAD = {0x30, 0x22, 0x60, 0x00, 0x78, 0x20, 0x00};

        ISOMsg requestIsoMsg = new ISOMsg();
        requestIsoMsg.setPackager(new ISO87DPackager());

        // Print the Message
        log.info("Received Length : " + rawRequest.length);
        log.info("Received: " + HexUtils.toHexString(rawRequest));


        // Discard the Unnecessary fields like Length, TPDU and Other params
        // LEN(2 Bytes) + ADR(1) + CB(1) + TPDU(5) + ISO PAYLOAD + CRC-16
        System.arraycopy(rawRequest, ADD_PACKET_LENGTH, isoPayLoad, 0, isoPayloadLength);
        log.info("ISO REQ: " + HexUtils.toHexString(isoPayLoad));

        // Parse the rest of the bytes buffer and unpack the payload
        requestIsoMsg.unpack(isoPayLoad);
        requestIsoMsg.dump(System.out, "\t");
        if ( requestIsoMsg.getMTI().equalsIgnoreCase("0200")
                && (requestIsoMsg.getString(4).equals("000000000123") || requestIsoMsg.getString(4).equals("009999999999"))
                ) {
            // Simulate Timeout for 20 seconds if amount is 123
            log.info("Simulating Timeout for 20 seconds");
            return;
        }

        // Ask MessageRespondMatcher to identify and Get the response Message
        ISOMsg responseIsoMsg = messageResponderMatcher.respond(requestIsoMsg);

        // Print the message
        responseIsoMsg.dump(System.out, "\t");

        // Add length and others
        byte[] rawIsoRespPayload = responseIsoMsg.pack();
        byte[] lengthResponse = YcsUtils.getPacketLength(rawIsoRespPayload.length + (ADD_PACKET_LENGTH-2));
        byte[] responsePayload = YcsUtils.concatenate(lengthResponse, ADD_ON_PAYLOAD, rawIsoRespPayload);
        log.info("Send: " + HexUtils.toHexString(responsePayload));
        // Push the packet
        ctx.writeAndFlush(Unpooled.wrappedBuffer(lengthResponse, ADD_ON_PAYLOAD, rawIsoRespPayload));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

    public byte[] computeHDLCCRC(byte[] data) {
        int crc = 0xFFFF;
        int polynomial = 0x1021;

        for (byte b : data) {
            for (int i = 0; i < 8; i++) {
                boolean bit = ((b >> (7 - i) & 1) == 1);
                boolean c15 = ((crc >> 15 & 1) == 1);
                crc <<= 1;
                if (c15 ^ bit) {
                    crc ^= polynomial;
                }
            }
        }

        // Final XOR
        crc ^= 0xFFFF;

        // Trim to 16-bit
        crc &= 0xFFFF;
        byte[] testCLC = ByteBuffer.allocate(4).putInt(crc).array();
        return new byte[] {testCLC[2], testCLC[3]};
    }
}
