package com.xhpc.pp.server;

import cn.hutool.core.date.DateUtil;
import com.alibaba.nacos.api.exception.NacosException;
import com.xhpc.common.api.dto.ChargingStationDto;
import com.xhpc.pp.domain.ServiceField;
import com.xhpc.pp.logic.FieldLogic;
import com.xhpc.pp.logic.ServiceMainLogic;
import com.xhpc.pp.tx.ServiceParameter;
import com.xhpc.pp.tx.ServiceResult;
import com.xhpc.pp.tx.TxException;
import com.xhpc.pp.utils.HexUtils;
import com.xhpc.pp.utils.SpringContextHolder;
import com.xhpc.pp.utils.security.CRCCalculator;
import org.apache.commons.lang3.ArrayUtils;
import org.quickserver.net.server.ClientBinaryHandler;
import org.quickserver.net.server.ClientHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;

import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMAT;
import static com.xhpc.common.data.redis.StaticBeanUtil.REDIS;
import static com.xhpc.common.data.redis.StaticBeanUtil.seqHex;
import static com.xhpc.pp.server.ChargingPileServer.removeHandler;
import static com.xhpc.pp.tx.ServiceResult.OK;
import static com.xhpc.pp.utils.HexUtils.toHex;
import static com.xhpc.pp.utils.security.CP56Time2a.toCp56Hex;
import static com.xhpc.pp.utils.security.CRCCalculator.calcCrc;
import static com.xhpc.pp.utils.security.CacheDataUtils.hori2;

public class ChargingPileBinaryHandler implements ClientBinaryHandler {

    private static final Logger log = LoggerFactory.getLogger(ChargingPileBinaryHandler.class);

    private static final String SERVICE_REGISTER = "01";
    private static final String SERVICE_HB = "03";
    private static final String SERVICE_RMR = "09";
    private static final String SERVICE_OD = "3B";
    private static final String SERVICE_RMCR = "57";

    private static final String DATA_TYPE_STRING = "string";
    private static final String DATA_TYPE_INT = "int";
    private static final String DATA_TYPE_LONG = "long";
    // private static final String DATA_TYPE_HEX = "hex";

    private final ServiceMainLogic servicemainLogic;


    public ChargingPileBinaryHandler() {

        servicemainLogic = SpringContextHolder.getBean("ServiceMainLogic");
    }

    @Override
    public void handleBinary(ClientHandler handler, byte[] data) throws IOException {

        try {
            List<byte[]> dataList = parseDataList(data);
            for (byte[] d : dataList) {
                String dataStr = toHex(d);
                String pileNo = ChargingPileServer.getPileNo(handler);
                log.info("received data <<<< |{}| from pile <- ({})", dataStr, pileNo);
                if (d.length <= 2 || !dataStr.startsWith("68")) {
                    log.info("received invalid data <<<< |{}|, len[{}]", dataStr, d.length);
                    continue;
                }
                int len = HexUtils.toInteger(d, 1, 2);
                if (d.length < len) {
                    log.error("incorrect input data length {}", d.length);
                    continue;
                }
                String crc = calcCrc(d);
                if (!dataStr.endsWith(crc)) {
                    log.error("incorrect input data crc {}", crc);
                    continue;
                }
                process(handler, d);
            }
        } catch (TxException | NacosException | InterruptedException e) {
            log.error(e.getMessage(), e);
        }
    }

    private void process(ClientHandler handler, byte[] data) throws TxException, IOException, NacosException, InterruptedException {

        String serviceName = toHex(data, 5, 6);
        String version = ChargingPileServer.getVersion(handler.getName());
        Map<String, Object> req = analysis(data, serviceName, version);
        int seq = HexUtils.toInteger(data, 2, 4);
        req.put("seqint", seq);
        String pileNo = (String) req.get("pileNo");
        ServiceParameter sp = new ServiceParameter(serviceName, pileNo, req);
        ServiceResult result = servicemainLogic.process(sp);
        String resultCode = result.getCode();
        String pilekey = "pile:".concat(pileNo);
        Map<String, Object> cachePile = REDIS.getCacheMap(pilekey);
        if (SERVICE_REGISTER.equals(serviceName) && OK.equals(resultCode)) {
            cachePile.put("tcfg", false);
            REDIS.setCacheMap(pilekey, cachePile);
            regHandler(handler, pileNo, req);
        }
        if (SERVICE_RMR.equals(serviceName) && OK.equals(resultCode)) {
            setCachePileRM(pilekey);
        }
        if (result.getBinary() != null) {
            String gunId = (String) req.get("gunId");
            if (gunId != null) {
                hori2(pileNo, gunId);
            }
            if (!handler.isOpen()) {
                log.error("send message failed. [{}]({}) connection lost", handler.getName(), pileNo);
                removeHandler(pileNo);
            } else {
                log.info("server send msg >>>> ({}) |{}|", pileNo, toHex(result.getBinary()));
                handler.sendClientBinary(result.getBinary());
            }
            if (SERVICE_HB.equals(serviceName) && OK.equals(resultCode)) {
                Boolean tcfg = (Boolean) cachePile.get("tcfg");
                if (!tcfg) {
                    String skey = pilekey.concat(".seqhex");
                    Thread.sleep(500);
                    Date date = Calendar.getInstance().getTime();
                    String timebin = getTimeBin(seqHex(skey), pileNo, date);
                    cachePile.put("configTime", DateUtil.format(date, NORM_DATETIME_FORMAT));
                    REDIS.setCacheMap(pilekey, cachePile);
                    log.info("server send time config msg >>>> ({}) |{}|", pileNo, timebin);
                    if (!handler.isOpen()) {
                        log.error("send message failed. [{}]({}) connection lost", handler.getName(), pileNo);
                        removeHandler(pileNo);
                    } else {
                        handler.sendClientBinary(HexUtils.toBytes(timebin));
                        if (gunId != null) {
                            hori2(pileNo, gunId);
                        }
                        cachePile.put("tcfg", true);
                        REDIS.setCacheMap(pilekey, cachePile);
                    }
                }
            }
        }
    }

    private String getTimeBin(String seqhex, String pileNo, Date date) {

        String timebin = "6812".concat(seqhex).concat("0056").concat(pileNo).concat(toCp56Hex(date));
        timebin = timebin.concat(CRCCalculator.calcCrc(timebin));
        return timebin;
    }

    private void regHandler(ClientHandler handler, String pileNo, Map<String, Object> req) {

        ChargingPileServer.putHandler(pileNo, handler);
        ChargingPileServer.putVersion(handler.getName(), (String) req.get("version"));
    }

    private void setCachePileRM(String pilekey) {

        Map<String, Object> cachePile = REDIS.getCacheMap(pilekey);
        ChargingStationDto cacheStation = REDIS.getCacheObject("station:".concat(cachePile.get("stationId").toString()));
        cachePile.put("rateModelId", cacheStation.getRateModelId());
        REDIS.setCacheMap(pilekey, cachePile);
        log.info("rate model responds success√");
    }

    private List<byte[]> parseDataList(byte[] data) {

        List<byte[]> dataList = new ArrayList<>();
        int processedLen, len = HexUtils.toInteger(data, 1, 2) + 4;
        processedLen = 0;
        int start = 0;
        while (start < data.length) {
            if (len > data.length) {
                log.error("incorrect input data|{}| len[{}]", data, data.length);
                break;
            }
            dataList.add(ArrayUtils.subarray(data, start, start + len));
            processedLen += len;
            start = processedLen;
            len = HexUtils.toInteger(data, start + 1, start + 2) + 4;
        }
        if (dataList.size() > 1) {
            log.info("detected stick package size[{}]", dataList.size());
        }
        return dataList;
    }

    private Map<String, Object> analysis(byte[] data, String service, String ver) throws TxException {

        List<ServiceField> fieldList = FieldLogic.fieldList(ver, service);
        if (fieldList == null || fieldList.isEmpty())
            throw TxException.INNER_ERROR("field mapper not found");
        Map<String, Object> result = new HashMap<String, Object>();
        int pos = 6;
        for (ServiceField field : fieldList) {
            if (pos >= data.length || pos + field.getLen() > data.length)
                break;
            switch (field.getDataType()) {
                case DATA_TYPE_STRING:
                    result.put(field.getCode(), HexUtils.toString(data, pos, pos + field.getLen()));
                    break;
                case DATA_TYPE_INT:
                    result.put(field.getCode(), HexUtils.byte2int(ArrayUtils.subarray(data, pos, pos + field.getLen())));
                    break;
                case DATA_TYPE_LONG:
                    result.put(field.getCode(), HexUtils.byte2Long(ArrayUtils.subarray(data, pos, pos + field.getLen())));
                    break;
                default:
                    result.put(field.getCode(), toHex(data, pos, pos + field.getLen()));
            }
            pos += field.getLen();
        }
        result.put("hex", toHex(data));
        result.put("seqhex", getSeq(data));
        return result;
    }

    private String getSeq(byte[] data) {

        return toHex(ArrayUtils.subarray(data, 3, 5));
    }

}
