package org.jpos.tcpay.acquirer.ysp;

import com.google.gson.Gson;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.jpos.iso.ISOException;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.packager.ISO87BPackager;
import org.jpos.tcpay.data_element.AdditionalData;
import org.jpos.tcpay.db.entity.AcquirerTerminal;
import org.jpos.tcpay.db.entity.PosTempTransaction;
import org.jpos.tcpay.db.repository.impl.AcquirerTerminalBatchRepositoryImpl;
import org.jpos.tcpay.db.repository.impl.AcquirerTerminalStanRepositoryImpl;
import org.jpos.tcpay.service.StanBatchNumberService;
import org.jpos.tcpay.model.IsoAcquirerTerminal;
import org.jpos.util.AppLogger;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Optional;

public class YspRequestManager {
    private AppLogger logger = new AppLogger();


    // Stan and Batch number management service
    private final StanBatchNumberService stanBatchService = new StanBatchNumberService(
            new AcquirerTerminalStanRepositoryImpl(),
            new AcquirerTerminalBatchRepositoryImpl()
    );

    int[] SUPPORTED_FIELDS = {2,3,4,11,12,13,14,22,23,24,25,35,37,38,39,41,42,49,52,55,62,63};


    public ISOMsg getReversal(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) throws ISOException {
        ISOMsg  acqRequest = (ISOMsg) isoMsg.clone(SUPPORTED_FIELDS);
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI("0400");
        acqRequest.setDirection(ISOMsg.OUTGOING);

        // TODO: Fetch the Txn details from DB using POS_STAN from Reversal table
        AdditionalData additionalData = getOriginalInfo(isoMsg);
        acqRequest.set(11, additionalData.getOriginalStan());
        acqRequest.set(19, isoAcquirerTerminal.getAcquiringInstCode());
        acqRequest.set(24, isoAcquirerTerminal.getNii());
        acqRequest.set(41, isoAcquirerTerminal.getTid());
        acqRequest.set(42, isoAcquirerTerminal.getMid());
        acqRequest.unset(63);
        acqRequest.set(90, getField90(additionalData));

        if(!populateBatchNumbers(acqRequest, (AcquirerTerminal) metadata.get("ACQ_TERMINAL"))) {
            return null;
        }

        return acqRequest;
    }

    public ISOMsg getRefundMsg(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) throws ISOException {
        ISOMsg  acqRequest = (ISOMsg) isoMsg.clone(SUPPORTED_FIELDS);
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI("0200");
        acqRequest.setDirection(ISOMsg.OUTGOING);


        if(!populateBatchNumbers(acqRequest, (AcquirerTerminal) metadata.get("ACQ_TERMINAL"))) {
            return null;
        }
        String time = Optional.ofNullable(isoMsg.getString(12)).filter(x  -> !(x.isEmpty())).orElse(getTime());
        acqRequest.set(19, isoAcquirerTerminal.getAcquiringInstCode());
        acqRequest.set(24, isoAcquirerTerminal.getNii());
        acqRequest.set(41, isoAcquirerTerminal.getTid());
        acqRequest.set(42, isoAcquirerTerminal.getMid());
        acqRequest.set(37, time + StringUtils.leftPad(isoMsg.getString(11), 6, '0'));
        acqRequest.set(53, isoMsg.getString(63));
        acqRequest.unset(63);

        if (!acqRequest.hasField(62)) {
            acqRequest.set(62, isoMsg.getString(11));
        }

        return acqRequest;
    }

    public ISOMsg getVoidMsg(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) throws ISOException {

        ISOMsg  acqRequest = (ISOMsg) isoMsg.clone(SUPPORTED_FIELDS);
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI("0220");
        acqRequest.setDirection(ISOMsg.OUTGOING);
        acqRequest.unset(35);
        acqRequest.unset(37);
        acqRequest.unset(38);
        acqRequest.unset(47);
        acqRequest.unset(52);
        acqRequest.unset(53);
        acqRequest.unset(63);
        String time = getTime();
        acqRequest.set(12, time);
        acqRequest.set(13, getDate());
        if(!populateBatchNumbers(acqRequest, (AcquirerTerminal) metadata.get("ACQ_TERMINAL"))) {
            return null;
        }

        // TODO: Fetch the Txn details from DB using POS_STAN from Original Txn table
        AdditionalData additionalData = getOriginalInfo(isoMsg);
        acqRequest.set(11, additionalData.getOriginalStan());
        acqRequest.set(12, additionalData.getOriginalTime());
        acqRequest.set(19, isoAcquirerTerminal.getAcquiringInstCode());
        acqRequest.set(24, isoAcquirerTerminal.getNii());
        acqRequest.set(41, isoAcquirerTerminal.getTid());
        acqRequest.set(42, isoAcquirerTerminal.getMid());
        acqRequest.set(60, isoMsg.getString(4));
        acqRequest.set(90, getField90(additionalData));


        return acqRequest;
    }

    public AdditionalData getOriginalInfo(ISOMsg isoMsg) {
        Gson gson = new Gson();
        AdditionalData additionalData = gson.fromJson(isoMsg.getString(47), AdditionalData.class);
        return additionalData;
    }

    public String getField90(AdditionalData additionalData) {
        String origTxnDetail = StringUtils.leftPad(additionalData.getOriginalMti(), 4, '0')
                + StringUtils.leftPad(additionalData.getOriginalStan(), 6, '0')
                + StringUtils.leftPad(additionalData.getOriginalDate().substring(4), 4, '0')
                + StringUtils.leftPad(additionalData.getOriginalTime(), 6, '0')
                + "00000000000";

        return origTxnDetail;
    }



    public ISOMsg getPreauthMsg(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) throws ISOException {

        ISOMsg  acqRequest = (ISOMsg) isoMsg.clone(SUPPORTED_FIELDS);
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI(isoMsg.getMTI());
        acqRequest.setDirection(ISOMsg.OUTGOING);
        acqRequest.set(3, "330000");
        String time = Optional.ofNullable(isoMsg.getString(12)).filter(x  -> !(x.isEmpty())).orElse(getTime());
        if(!populateStanAndBatchNumbers(acqRequest, (AcquirerTerminal) metadata.get("ACQ_TERMINAL"))) {
            return null;
        }

        PosTempTransaction tempTransaction = (PosTempTransaction) metadata.get("TEMP_TRANSACTION");

        acqRequest.set(12, tempTransaction.getBTidTime());
        acqRequest.set(13, tempTransaction.getBTidDate().substring(4));

        acqRequest.set(19, isoAcquirerTerminal.getAcquiringInstCode());
        acqRequest.set(24, isoAcquirerTerminal.getNii());
        acqRequest.set(41, isoAcquirerTerminal.getTid());
        acqRequest.set(42, isoAcquirerTerminal.getMid());
        acqRequest.set(37, time + StringUtils.leftPad(isoMsg.getString(11), 6, '0'));
        acqRequest.set(53, isoMsg.getString(63));
        acqRequest.unset(63);

        updatePosTempTransaction(tempTransaction, acqRequest);

        return acqRequest;
    }

    private void updatePosTempTransaction(PosTempTransaction tempTransaction, ISOMsg acqRequest) {
        tempTransaction.setBTidStan(acqRequest.getString(11));
        tempTransaction.setBTidBatchNo(acqRequest.getString(62));
        tempTransaction.setEntryMode(acqRequest.getString(22));
        tempTransaction.setProcCode(acqRequest.getString(3));
        tempTransaction.setReferenceNo(acqRequest.getString(37));
    }

    public ISOMsg getSaleMsg(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) throws ISOException {

        ISOMsg  acqRequest = (ISOMsg) isoMsg.clone(SUPPORTED_FIELDS);
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI(isoMsg.getMTI());
        acqRequest.setDirection(ISOMsg.OUTGOING);
        if ("000001".equals(isoMsg.getString(3))) {
            acqRequest.set(3, "000000");
        }
        String time = Optional.ofNullable(isoMsg.getString(12)).filter(x  -> !(x.isEmpty())).orElse(getTime());
        if(!populateStanAndBatchNumbers(acqRequest, (AcquirerTerminal) metadata.get("ACQ_TERMINAL"))) {
            return null;
        }

        PosTempTransaction tempTransaction = (PosTempTransaction) metadata.get("TEMP_TRANSACTION");

        acqRequest.set(12, tempTransaction.getBTidTime());
        acqRequest.set(13, tempTransaction.getBTidDate().substring(4));

        acqRequest.set(19, isoAcquirerTerminal.getAcquiringInstCode());
        acqRequest.set(24, isoAcquirerTerminal.getNii());
        acqRequest.set(41, isoAcquirerTerminal.getTid());
        acqRequest.set(42, isoAcquirerTerminal.getMid());
        acqRequest.set(37, time + StringUtils.leftPad(isoMsg.getString(11), 6, '0'));
        acqRequest.set(53, isoMsg.getString(63));
        acqRequest.unset(63);
        updatePosTempTransaction(tempTransaction, acqRequest);


        return acqRequest;
    }

    public ISOMsg getCompletionMsg(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) throws ISOException {

        ISOMsg  acqRequest = (ISOMsg) isoMsg.clone(SUPPORTED_FIELDS);
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI(isoMsg.getMTI());
        acqRequest.setDirection(ISOMsg.OUTGOING);
        String time = Optional.ofNullable(isoMsg.getString(12)).filter(x  -> !(x.isEmpty())).orElse(getTime());
        if(!populateStanAndBatchNumbers(acqRequest, (AcquirerTerminal) metadata.get("ACQ_TERMINAL"))) {
            return null;
        }

        PosTempTransaction tempTransaction = (PosTempTransaction) metadata.get("TEMP_TRANSACTION");

        acqRequest.set(12, tempTransaction.getBTidTime());
        acqRequest.set(13, tempTransaction.getBTidDate().substring(4));
        acqRequest.set(19, isoAcquirerTerminal.getAcquiringInstCode());
        acqRequest.set(24, isoAcquirerTerminal.getNii());
        acqRequest.set(41, isoAcquirerTerminal.getTid());
        acqRequest.set(42, isoAcquirerTerminal.getMid());
        acqRequest.set(37, time + StringUtils.leftPad(isoMsg.getString(11), 6, '0'));
        acqRequest.set(53, isoMsg.getString(63));
        acqRequest.unset(63);
        updatePosTempTransaction(tempTransaction, acqRequest);

        return acqRequest;
    }


    @SneakyThrows
    public ISOMsg getNetworkMsgEcho(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) {
        ISOMsg acqRequest = new ISOMsg();
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI("0800");
        acqRequest.set(7, isoMsg.getString(7));
        acqRequest.set(11, isoMsg.getString(11));
        acqRequest.set(70, "301");

        return acqRequest;
    }


    @SneakyThrows
    public ISOMsg getNetworkMsgTmK(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) {
        String MY_KEY = "0123456789ABCDEF0123456789ABCDEF";
        ISOMsg acqRequest = new ISOMsg();
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI("0800");
        acqRequest.set(3, "991380");
        if(!populateStanAndBatchNumbers(acqRequest, (AcquirerTerminal) metadata.get("ACQ_TERMINAL"))) {
            return null;
        }
        PosTempTransaction tempTransaction = (PosTempTransaction) metadata.get("TEMP_TRANSACTION");

        acqRequest.set(24, isoAcquirerTerminal.getNii());
        acqRequest.set(41, isoAcquirerTerminal.getTid());
        acqRequest.set(42, isoAcquirerTerminal.getMid());
        acqRequest.set(43, "Mercury PAY");
        acqRequest.set(63, isoMsg.getString(62));
        acqRequest.set(125, MY_KEY);
        updatePosTempTransaction(tempTransaction, acqRequest);
        return acqRequest;
    }


    @SneakyThrows
    public ISOMsg getNetworkMsgPinIpek(ISOMsg isoMsg, IsoAcquirerTerminal isoAcquirerTerminal, Map<String, Object> metadata) {
        ISOMsg acqRequest = new ISOMsg();
        acqRequest.setPackager(new ISO87BPackager());

        acqRequest.setMTI("0800");
        acqRequest.set(3, "990280");
        acqRequest.set(7, getDateTime());
        acqRequest.set(11, isoMsg.getString(11));
        acqRequest.set(12, getTime());
        acqRequest.set(41, isoAcquirerTerminal.getTid());
        acqRequest.set(42, isoAcquirerTerminal.getMid());
        acqRequest.set(53, isoMsg.getString(63));
        acqRequest.set(70, "161");
        return acqRequest;
    }


    private String getDateTime() {
        LocalDateTime dateTime = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMddHHmmss");
        return dateTime.format(formatter);
    }

    private String getTime() {
        LocalDateTime dateTime = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmmss");
        return dateTime.format(formatter);
    }

    private String getDate() {
        LocalDateTime dateTime = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMdd");
        return dateTime.format(formatter);
    }

    /**
     * Populate Stan (DE11) and Batch (DE62) numbers from database if applicable
     */
    private boolean populateStanAndBatchNumbers(ISOMsg request, AcquirerTerminal acquirerTerminalId) {
        try {
            // Get Stan number from database if transaction qualifies
            Integer nextStan = stanBatchService.getNextStanNumber(acquirerTerminalId, request);
            if (nextStan != null) {
                request.set(11, String.format("%06d", nextStan));
            }

            // Get Batch number from database if transaction qualifies
            Integer nextBatch = stanBatchService.getBatchNumber(acquirerTerminalId, request);
            if (nextBatch != null) {
                request.set(62, String.format("%06d", nextBatch));
            }

        } catch (Exception e) {
            logger.exceptionLog(e);
            // Continue with existing logic if database operations fail
            return false;
        }
        return true;
    }

    private boolean populateBatchNumbers(ISOMsg request, AcquirerTerminal acquirerTerminalId) {
        try {
            // Get Batch number from database if transaction qualifies
            Integer nextBatch = stanBatchService.getBatchNumber(acquirerTerminalId, request);
            if (nextBatch != null) {
                request.set(62, String.format("%06d", nextBatch));
            }

        } catch (Exception e) {
            logger.exceptionLog(e);
            // Continue with existing logic if database operations fail
            return false;
        }
        return true;
    }
}
