package com.xhpc.pp.logic;

import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xhpc.common.api.CardService;
import com.xhpc.common.api.PileOrderService;
import com.xhpc.common.core.constant.Constants;
import com.xhpc.common.core.domain.R;
import com.xhpc.common.data.up.PileStartChargingData;
import com.xhpc.common.domain.IccardInfo;
import com.xhpc.common.domain.IccardInfoExample;
import com.xhpc.common.enums.StationDeviceEnum;
import com.xhpc.pp.domain.XhpcDeviceMessage;
import com.xhpc.pp.mapper.IccardInfoMapper;
import com.xhpc.pp.mapper.XhpcDeviceMessageMapper;
import com.xhpc.pp.tx.ServiceParameter;
import com.xhpc.pp.tx.ServiceResult;
import com.xhpc.pp.tx.logic.ServiceLogic;
import com.xhpc.pp.utils.HexUtils;
import com.xhpc.pp.utils.security.CRCCalculator;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.xhpc.common.core.constant.Constants.INVALID_OPERATOR;
import static com.xhpc.common.data.redis.StaticBeanUtil.REDIS;
import static com.xhpc.common.data.redis.StaticBeanUtil.seqHex;

@Lazy
@Component("PileStartChargingDataLogic")
public class PileStartChargingDataLogic implements ServiceLogic {

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

    @Resource
    private XhpcDeviceMessageMapper deviceMessageMapper;
    @Resource
    private IccardInfoMapper iccardInfoMapper;
    @Resource
    private CardService cardService;

    @Resource
    PileOrderService pileOrderService;

    @Override
    public ServiceResult service(ServiceParameter sp) throws Exception {
        String resultStr = "00";
        String result = ServiceResult.HEX_00;
        Map<String, Object> req = sp.getParameters();
        ObjectMapper objectMapper = new ObjectMapper();
        PileStartChargingData pileStartChargingData = objectMapper.convertValue(req, PileStartChargingData.class);
        String connectorId = pileStartChargingData.getPileNo().concat(pileStartChargingData.getGunId());
        //todo 桩停用
        String accountOrCardNo = pileStartChargingData.getAccountOrCardNo();
        String corpNo = connectorId.substring(0, 6);
        String balance = "00000000";
        String cardNo = "0000000000000000";
        String orderNo = "00000000000000000000000000000000";
        if (pileStartChargingData.getStartType().equals("01")) { //刷卡启动
            IccardInfoExample example = new IccardInfoExample();
            IccardInfoExample.Criteria criteria = example.createCriteria();
            String physicCardNo;
            if (accountOrCardNo.startsWith("00000000")) {
                physicCardNo = accountOrCardNo.substring(8);
            } else if (accountOrCardNo.endsWith("00000000")) {
                physicCardNo = accountOrCardNo.substring(0, 8);
            } else {
                physicCardNo = accountOrCardNo;
            }
            criteria.andCardidEqualTo(physicCardNo);//.andCorpnoEqualTo(corpNo); // todo remove corpNo alrity
            List<IccardInfo> iccardInfos = iccardInfoMapper.selectByExample(example);
            if (iccardInfos.size() == 1) {
                IccardInfo iccardInfo = iccardInfos.get(0);
                if (iccardInfo.getCardtype() == 0) {
                    resultStr = INVALID_OPERATOR;
                } else {
                    // 调用订单接口
                    cardNo = iccardInfo.getCardno();
                    Integer rateModelId = pileStartChargingData.getRateModelId(); // todo not very strict.
                    if (rateModelId == null)
                        rateModelId = ((Long) REDIS.getCacheMapValue("pile:".concat(connectorId.substring(0, 14)),
                                "rateModelId")).intValue();
                    R r = cardService.cardStartup(cardNo, connectorId, rateModelId.toString());
                    int code = r.getCode();
                    if (code == 200) {
                        result = ServiceResult.HEX_01;
                        Map data = (Map) r.getData();
                        orderNo = (String) data.get("serialNumber");
                        Double dBalance = (Double) data.get("balance");
                        dBalance = dBalance * 100;
                        balance = HexUtils.toHexInt(dBalance.longValue());
                        Map<String, Object> cacheOrder = new HashMap<>();
                        String pileNo = connectorId.substring(0, 14);
                        String pkey = "pile:".concat(pileNo);
                        Long pileRateModelId = REDIS.getCacheMapValue(pkey, "rateModelId");
                        cacheOrder.put("pileRateModelId", pileRateModelId);
                        cacheOrder.put("rateModelId", rateModelId);
                        cacheOrder.put("pileNo", pileNo);
                        cacheOrder.put("initBalance", dBalance.intValue() * 100);
                        cacheOrder.put("startType", pileStartChargingData.getStartType());
                        cacheOrder.put("logicCardNo", cardNo);
                        cacheOrder.put("physicCardNo", accountOrCardNo);
                        cacheOrder.put("gunId", connectorId);
                        cacheOrder.put("startTime",
                                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()).format(Instant.now()));
                        String orderkey = "order:".concat(orderNo);
                        REDIS.setCacheMap(orderkey, cacheOrder);
                        REDIS.setCacheMapValue("gun:".concat(connectorId), "ac.on", false);
                        REDIS.setCacheMapValue("gun:".concat(connectorId), "orderkey", orderNo);

                        String pushOrderKey = orderkey.replace("order:", "pushOrder:");
                        Map<String, Object> map = new HashMap<>();
                        map.put("startChargeSeqStat", 2);
                        REDIS.setCacheMap(pushOrderKey, map);

                        resultStr = "00";
                    } else {
                        resultStr = "1C";
                        if(code < 600){
                            String errorMsg = r.getMsg();
                            if ( errorMsg != null && (errorMsg.startsWith("0") || errorMsg.startsWith("1")) ) {
                                resultStr = errorMsg;
                            }
                            log.error("pile start charging internalError: {}", resultStr);
                        } else {
                            resultStr = getFailReasonByCode(code);
                        }
                    }
                    cardNo = StringUtils.leftPad(iccardInfo.getCardno(), 16, "0");
                }
            } else {
                resultStr = "01";
            }
        } else if (pileStartChargingData.getStartType().equals("03")){  // VIN码充电
            if (pileStartChargingData.getVin().equals("0000000000000000000000000000000000")){
                return new ServiceResult(ServiceResult.FAIL);
            }

            String vinCode = asciiToString(pileStartChargingData.getVin());

            R r = pileOrderService.pileVin(connectorId, vinCode);
            int code = r.getCode();

            if (code == 200) {
                if(r.getData() == null){
                    return new ServiceResult(ServiceResult.FAIL);
                }
                Integer rateModelId = pileStartChargingData.getRateModelId(); // todo not very strict.
                if (rateModelId == null)
                    rateModelId = ((Long) REDIS.getCacheMapValue("pile:".concat(connectorId.substring(0, 14)),
                            "rateModelId")).intValue();

                result = ServiceResult.HEX_01;
                Map data = (Map) r.getData();
                orderNo = (String) data.get("serialNumber");
                Double dBalance = (Double) data.get("balance");
                dBalance = dBalance * 100;
                balance = HexUtils.toHexInt(dBalance.longValue());
                Map<String, Object> cacheOrder = new HashMap<>();
                String pileNo = connectorId.substring(0, 14);
                String pkey = "pile:".concat(pileNo);
                Long pileRateModelId = REDIS.getCacheMapValue(pkey, "rateModelId");
                cacheOrder.put("pileRateModelId", pileRateModelId);
                cacheOrder.put("rateModelId", rateModelId);
                cacheOrder.put("pileNo", pileNo);
                cacheOrder.put("startType", pileStartChargingData.getStartType());
                cacheOrder.put("initBalance", dBalance.intValue() * 100);
                cacheOrder.put("logicCardNo", cardNo);
                cacheOrder.put("physicCardNo", accountOrCardNo);
                cacheOrder.put("gunId", connectorId);
                cacheOrder.put("startTime",
                        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()).format(Instant.now()));
                String orderkey = "order:".concat(orderNo);
                REDIS.setCacheMap(orderkey, cacheOrder);
                REDIS.setCacheMapValue("gun:".concat(connectorId), "ac.on", false);
                resultStr = "00";
            } else {
                resultStr = Constants.INTERNAL_ERROR;
                if(code < 600){
                    String errorMsg = r.getMsg();
                    if ( errorMsg != null && (errorMsg.startsWith("0") || errorMsg.startsWith("1")) ) {
                        resultStr = errorMsg;
                    }
                    log.error("pile start charging internalError: {}", errorMsg);
                } else {
                    resultStr = getFailReasonByCode(code);
                }
            }
        }
        String skey = "gun:".concat(connectorId).concat(".seqhex");
        String seq =(String) req.get("hex");
        String hex =
                "682A".concat(seq.substring(4,8)).concat("0032").concat(orderNo).concat(connectorId).concat(cardNo).concat(balance).concat(result).concat(resultStr);
        hex = hex.concat(CRCCalculator.calcCrc(hex));

        //Integer startFlag = result.equals(ServiceResult.HEX_01) ? 2: 4;

        String pushOrderKey = "pushOrder:".concat(orderNo);
        Map<String, Object> pushOrder = REDIS.getCacheMap(pushOrderKey);
        pushOrder.put("startChargeSeqStat", 2);
//        pushOrder.put("startTime", DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
//        pushOrder.put("connectorID", connectorId);
//        pushOrder.put("startChargeNotificationStat", 0);
//        pushOrder.put("chargeOrderInfoNotificationStat", 0);
//        pushOrder.put("isStopNotified", false);
        REDIS.setCacheMap(pushOrderKey,pushOrder);


        String remark = "充电桩发起充电";
        XhpcDeviceMessage deviceMessage = new XhpcDeviceMessage();
        deviceMessage.setType(StationDeviceEnum.PILE.getCode());
        deviceMessage.setSerialNumber(connectorId);
        deviceMessage.setRemark(remark);
        deviceMessage.setStatus(0);
        deviceMessage.setContent((String) req.get("hex"));
        deviceMessageMapper.insertByDomain(deviceMessage);

        return new ServiceResult(HexUtils.toBytes(hex), result);
    }

    private static String getFailReasonByCode(int code){
        String failReason = "";
        switch (code){
            case 1888: failReason = Constants.EMPTY_VIN; break;
            case 1880: failReason = Constants.INVALID_VIN; break;
            case 1881: failReason = Constants.UNEXIST_VIN; break;
            case 1104: failReason = Constants.UNUSE_TERMINAL; break;
            case 1103: failReason = Constants.EXCEPTION_ORDER; break;
            case 1100: failReason = Constants.INSUFFICIENT_ACCOUNT; break;
            case 1101: failReason = Constants.REFUND_ORDER; break;
            case 1102: failReason = Constants.CHARGE_ORDER; break;
            case 1105: failReason = Constants.UNREGISTERED_TERMINAL; break;
            case 1106:
            case 1008: failReason = Constants.UNKNOWN_GUN; break;
            case 1107: failReason = Constants.OFFLINE_TERMINAL; break;
            case 1109: failReason = Constants.INSERT_THE_GUN; break;
            case 1111: failReason = Constants.FAULT_TERMINAL; break;
            case 1112: failReason = Constants.CHARGE_TERMINAL; break;
            case 1200: failReason = Constants.INVALID_USER; break;
            case 1201: failReason = Constants.INVALID_OPERATOR; break;
            case 1202: failReason = Constants.OFFLINE_CARD; break;
            case 1203: failReason = Constants.ZERO_CARD; break;
            case 1204: failReason = Constants.TWO_CARD; break;
            case 1205: failReason = Constants.THREE_CARD; break;
            case 1206: failReason = Constants.FOUR_CARD; break;
            case 1207: failReason = Constants.INVALID_CARD; break;
            case 1208:
                failReason = Constants.INVALID_RATE_MODE; break;

            default:  failReason = Constants.INTERNAL_ERROR; break;
        }

        return failReason;
    }


    public static String asciiToString(String text) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            if (text.charAt(i) == '1' && i < text.length() - 2) {
                int code = Integer.parseInt(text.substring(i, i + 3), 16);
                builder.append((char) code);
                i += 2;
            } else if (i < text.length() - 1) {
                int code = Integer.parseInt(text.substring(i, i + 2), 16);
                builder.append((char) code);
                i += 1;
            }
        }
        return builder.toString();
    }

}
