package com.brilliance.isc.common.gmgf.service.impl;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.XML;
import com.brilliance.isc.bo.*;
import com.brilliance.isc.bo.model.EtrCli;
import com.brilliance.isc.bo.model.GfPdf;
import com.brilliance.isc.common.cache.pty.PtyCacheService;
import com.brilliance.isc.common.context.SettleContext;
import com.brilliance.isc.common.counter.service.CounterService;
import com.brilliance.isc.common.doc.TextResultVo;
import com.brilliance.isc.common.esb.gmgf.IGmgfFeign;
import com.brilliance.isc.common.exception.CommonServiceException;
import com.brilliance.isc.common.gmgf.service.GmgfService;
import com.brilliance.isc.common.tools.service.ObjectStroeToolService;
import com.brilliance.isc.common.transaction.vo.BaseTransactionVo;
import com.brilliance.isc.common.util.Utils;
import com.brilliance.isc.common.vo.AbstractCommonVo;
import com.brilliance.isc.common.vo.BizInfoVo;
import com.brilliance.isc.esb.model.gmgf.CusAmtList;
import com.brilliance.isc.esb.model.gmgf.Ge57Data;
import com.brilliance.isc.esb.model.gmgf.Ge59Data;
import com.brilliance.isc.esb.model.inner.*;
import com.brilliance.isc.mda.dao.*;
import com.brilliance.isc.utils.DateUtils;
import com.brilliance.isc.utils.StringUtils;
import com.brilliance.isc.vo.PtsptaInfoVo;
import com.brilliance.isc.vo.PtsptaQueryVo;
import com.brilliance.isc.vo.WarnVo;
import com.brilliance.isc.vo.model.BgdVo;
import com.brilliance.isc.vo.model.GmgfVo;
import com.brilliance.isc.vo.model.ShowMsg;
import com.brilliance.isc.warn.WarnService;
import com.brilliance.mda.runtime.mda.config.IniConfig;
import com.brilliance.mda.runtime.mda.util.Dates;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.util.*;

/**
 * 国贸公服
 */
@Slf4j
@Component
public class GmgfServiceImpl implements GmgfService {
    static IniConfig config;
    @Resource
    EtrCliMapper etrCliMapper;
    @Resource
    PtyCacheService ptyCacheService;
    @Resource
    private WarnService warnService;
    @Resource
    private StbMapper stbMapper;
    @Resource
    private GfPdfMapper gfPdfMapper;
    @Resource
    private CounterService counterService;
    @Resource
    private ObjectStroeToolService objectStoreService;
    @Autowired(required = false)
    private IGmgfFeign gmgfFeign;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private PtsptaMapper ptsptaMapper;
    @Resource
    private SmhMapper smhMapper;

    @PostConstruct
    public void loadConfig() {
        try {
            org.springframework.core.io.Resource resource = new ClassPathResource("cfg/gmgf.ini");
            config = IniConfig.loadIniFile("gmgf.ini", resource.getInputStream(), "utf-8");
        } catch (Exception e) {
            log.error("加载来报通用静态配置[rcvdoc.ini]异常", e);
            //弄个空的配置，避免影响其他程序
            config = new IniConfig();
        }
    }

    public boolean isVisual(Pty pty) {
        EtrCli condition = new EtrCli();
        condition.setFlag("0");
        condition.setCusCode(pty.getExtkey());
        if (etrCliMapper.count(condition) > 0) {
            return true;
        }
        return false;
    }

    public GmgfVo init(BaseTransactionVo vo, AbstractCommonVo xxxgrp) {
        try {
            GmgfVo gmgfVo = new GmgfVo();
            if (!SettleContext.isInitTransSta()){
                return gmgfVo;
            }
            String act = getAct(xxxgrp);
            if (StringUtils.isEmpty(act)) {
                return gmgfVo;
            }
            Pty pty = getPty(act);
            if (pty == null || !isVisual(pty) || "Y".equals(vo.getAutoFlag())) {
                return gmgfVo;
            }
            //设置为国贸公服组件可用
            gmgfVo.setVisual(true);
            //初始化统一社会信用代码
            if (StringUtils.hasText(pty.getIdcode())) {
                gmgfVo.setUnionOrgCode(pty.getIdcode());
            } else {
                gmgfVo.setUnionOrgCode(pty.getJuscod());
            }
            //初始化业务信息
            initBizInfo(vo, gmgfVo);
            //初始化金额信息
            gmgfVo.setMax(Utils.getFieldValue(xxxgrp, "cbs//nom1"));
            //初始化国贸公服信息
            initGmgfInfo(vo, gmgfVo);
            return gmgfVo;
        } catch (Exception e) {
            log.error("初始化国贸组件异常", e);
            return null;
        }
    }

    @Override
    public List<BgdVo> queryGdye(GmgfVo gmgfVo) {
        String unionOrgCode = gmgfVo.getUnionOrgCode();
        Assert.hasLength(unionOrgCode, () -> "报关单参考余额查询终止:统一社会信用代码为空.");
        String customNo = gmgfVo.getInitCustomNo();
        Assert.hasLength(customNo, () -> "报关单参考余额查询终止:报关单号为空.");
        Cbb maxcbb = gmgfVo.getMax();
        Assert.notNull(maxcbb, () -> "报关单参考余额查询终止:业务余额信息为空.");
        String cur = maxcbb.getCur();
        Stb stb = stbMapper.queryByCod("GMGFBZ", "EN", cur);
        Assert.notNull(stb, () -> "报关单参考余额查询终止:从码表GMGFBZ没找到" + cur + "币种对应的GB/T 12406-2008的币种代码.");
        String convertCur = stb.getTxt();
        InnerRes<InnerResGe57> res = DealGe57Interface(unionOrgCode, customNo, convertCur);
        assertByCode(res.header.code, "报关单参考余额查询接口调用失败", res.header.description);
        Assert.notNull(res.DATA.data,"报关单参考余额查询接口返回数据为空");
        Ge57Data ge57Data = res.DATA.data;
        List<CusAmtList> lst = ge57Data.getCusAmtList();
        if (ObjectUtils.isEmpty(lst)) {
            log.warn("未查到报关单参考余额信息：" + ge57Data.respMemo);
            return Collections.emptyList();
        }
        List<BgdVo> bgdList = new ArrayList<>();
        for (CusAmtList item : lst) {
            BgdVo bgd = new BgdVo();
            bgd.setCustomNo(item.getCustomNo());
            if (convertCur.equals(item.getCustomCurrency())) {
                bgd.setCustomCurrency(cur);
            } else {
                Stb showCur = stbMapper.queryByTxt("GMGFBZ", "EN", item.getCustomCurrency());
                if (Objects.isNull(showCur)) {
                    log.warn("根据国贸公服币种[{}]找不到对应码表信息", item.getCustomCurrency());
                } else {
                    bgd.setCustomCurrency(showCur.getCod());
                }
            }
            bgd.setRemainderValue(item.getRemainderValue());
            bgd.setCustomAmount(item.getCustomAmount());
            bgdList.add(bgd);
        }
        return bgdList;
    }

    @Override
    public TextResultVo queryGdmx(GmgfVo gmgfVo) {
        String unionOrgCode = gmgfVo.getUnionOrgCode();
        String orderNo = gmgfVo.getOrderNo();
        String customNo = gmgfVo.getChoiceCustomNo();
        Assert.hasLength(unionOrgCode, () -> "报关单信息查询终止:统一社会信用代码为空.");
        Assert.hasLength(orderNo, () -> "报关单信息查询终止:平台订单编号为空.");
        Assert.hasLength(customNo, () -> "报关单信息查询终止:报关单号为空.");
        InnerRes<InnerResGe59> res = DealGe59Interface(unionOrgCode, orderNo, customNo);
        assertByCode(res.header.code, "报关单信息查询接口调用失败", res.header.description);
        Assert.notNull(res.DATA.data,"报关单信息查询接口返回数据为空");
        Ge59Data ge59Data = res.DATA.data;
        List<ShowMsg> gdmxList = new ArrayList<>();
        Map<String, List> info = new HashMap<>();
        TextResultVo textResultVo = new TextResultVo();
        if (!ObjectUtils.isEmpty(ge59Data.customsInfos)) {
            //展示报关单信息列表
            info.put("报关单信息列表", ge59Data.customsInfos);
        } else if (!ObjectUtils.isEmpty(ge59Data.errCusList)) {
            //展示报关单异常提示列表
            info.put("报关单异常信息列表", ge59Data.errCusList);
        } else {
            log.warn("未查到报关单信息：" + ge59Data.respMemo);
            return textResultVo;
        }
        LinkedHashMap source = objectMapper.convertValue(info,
                new TypeReference<LinkedHashMap<String, LinkedList<LinkedHashMap>>>() {
                });
        generateShowModel("GE59", source, gdmxList, 0);

        textResultVo.setFormatData(gdmxList);
        textResultVo.setFormat("gmgf");
        return textResultVo;
    }

    @Override
    public String queryYzbg(GmgfVo gmgfVo) {
        String orderNo = gmgfVo.getOrderNo();
        Assert.hasLength(orderNo, () -> "验核报告调阅终止:平台订单编号为空.");
        String verifyrpid = "";
        //往gfpdf【公服PDF文件表】插入记录
        String inr = insertGfpdf("ge67", gmgfVo);
        DealGe67Interface(orderNo, verifyrpid, "1", "1", inr);
        return "验核报告调阅成功";
    }

    @Override
    public String queryImage(GmgfVo gmgfVo) {
        String orderNo = gmgfVo.getOrderNo();
        String customNo = gmgfVo.getChoiceCustomNo();
        Assert.hasLength(orderNo, () -> "报关单影像件调阅终止:平台订单编号为空.");
        Assert.hasLength(customNo, () -> "报关单影像件调阅终止:报关单号为空.");
        //往gfpdf【报关单影像件文件表】插入记录
        String inr = insertGfpdf("ge69", gmgfVo);
        DealGe69Interface(orderNo, customNo, inr);
        return "报关单影像件调阅成功";
    }

    @Override
    public Map<String, List> queryPdf(GmgfVo gmgfVo) {
        String orderNo = gmgfVo.getOrderNo();
        String customNo = gmgfVo.getChoiceCustomNo();
        Assert.hasLength(orderNo, () -> "查询pdf列表终止:平台订单编号为空.");
        Assert.hasLength(customNo, () -> "查询pdf列表终止:报关单号为空.");
        Map<String, List> rs = new HashMap<>();
        GfPdf conditon = new GfPdf();
        conditon.setOrderno(orderNo);
        conditon.setCustomno(customNo);
        conditon.setJktyp("ge67");
        List<GfPdf> yzbgList = gfPdfMapper.selectByCondition(conditon);
        rs.put("yzbgs", yzbgList);
        conditon.setJktyp("ge69");
        List<GfPdf> imgList = gfPdfMapper.selectByCondition(conditon);
        rs.put("imges", imgList);
        return rs;
    }

    @Override
    public TextResultVo showPdf(String inr) {
        GfPdf result = gfPdfMapper.selectByPrimaryKey(inr);
        Assert.notNull(result, () -> "pdf预览终止:根据主键" + inr + "未查询到gfpdf信息.");
        String path = result.getPath();
        String fileName = result.getFilename();
        File pdfFile = new File(path, fileName);
        String cleanPath = cleanPath(pdfFile.getPath());
        //本地不存在从对象存储下载
        byte[] dataBytes = objectStoreService.readObjectFile(cleanPath);
        TextResultVo textResultVo = new TextResultVo();
        textResultVo.setBinary(dataBytes);
        textResultVo.setFormat("pdf");
        textResultVo.setOwnref(result.getCustomno());
        return textResultVo;
    }

    /**
     * 初始化国贸公服信息
     */
    private void initGmgfInfo(BaseTransactionVo vo, GmgfVo gmgfVo) {
        try {
            Spt spt = vo.getSpt();
            if (spt == null) {
                log.error("待办信息为空");
                return;
            }
            String smhInr = spt.getSmhinr();
            if (StringUtils.isEmpty(smhInr)) {
                log.error("无关联smh");
                return;
            }
            String trnName = SettleContext.getDirectTransName();
            Smh smh = null;
            if (trnName.contains("CPTOPN")) {
                smh = smhMapper.getSmhInfoBySptSmhinr(smhInr);
            } else if (trnName.contains("CPTADV")) {
                smh = smhMapper.selectRelationSmh(smhInr, "<", "T2D");
            }
            if (smh == null) {
                log.error("获取smh信息为空");
                return;
            }
            StringBuilder path = new StringBuilder("/sstf/data/");
            path.append(smh.getDocpth()).append("/").append(smh.getDocfil()).append(".").append(smh.getDocfxt());
            String cleanPath = cleanPath(path.toString());
            byte[] bytes = objectStoreService.readObjectFile(cleanPath);
            String msg = new String(bytes, "utf-8");
            JSONObject jsonObject = XML.toJSONObject(msg);
            //平台订单编号
            String orderNo = jsonObject.getByPath("ROOT.MsgBody.CUSSEQ", String.class);
            //报关单单号
            String apyctm = jsonObject.getByPath("ROOT.MsgBody.APYCTM", String.class);
            if (StringUtils.hasText(apyctm)) {
                StringBuilder text = new StringBuilder();
                String[] parts = apyctm.split("\\|\\+\\|");
                for (int i = 0; i < parts.length; i++) {
                    String customNo = parts[i];
                    if (StringUtils.isNotEmpty(customNo)) {
                        if (i > 0) {
                            text.append("#");
                        }
                        text.append(customNo);
                    }
                }
                gmgfVo.setInitCustomNo(text.toString());
            }
            if (StringUtils.isNotEmpty(orderNo)) {
                gmgfVo.setOrderNo(orderNo);
                warnService.tipWarn("GMGF", "init", WarnVo.SOURCE_BACK,
                        "该笔业务存在外部核验结果供审核使用", new Object[]{orderNo});
            } else {
                warnService.tipWarn("GMGF", "init", WarnVo.SOURCE_BACK, null, new Object[]{orderNo});
            }
        } catch (Exception e) {
            log.error("解析平台订单号和报关单号失败", e);
        }
    }

    /**
     * 初始化业务信息
     */
    private void initBizInfo(BaseTransactionVo vo, GmgfVo gmgfVo) {
        if (StringUtils.isEmpty(vo.getTransName())) {
            vo.setTransName(SettleContext.getDirectTransName());
        }
        BizInfoVo bizInfoVo = SettleContext.invokeBizInfo(vo);
        BizInfoVo bizInfo = gmgfVo.getBizInfo();
        bizInfo.setObjtyp(bizInfoVo.getObjtyp());
        bizInfo.setObjinr(bizInfoVo.getObjinr());
        bizInfo.setOwnref(bizInfoVo.getOwnref());
    }

    /**
     * 往gfpdf【公服PDF文件表】插入记录
     */
    private String insertGfpdf(String jktyp, GmgfVo gmgfVo) {
        GfPdf gfPdf = new GfPdf();
        gfPdf.setInr(counterService.dbCounterFormat("GFPDF"));
        gfPdf.setObjinr(gmgfVo.getBizInfo().getObjinr());
        gfPdf.setObjtyp(gmgfVo.getBizInfo().getObjtyp());
        gfPdf.setSta("W");
        gfPdf.setCredat(DateUtils.dateTimeNow("yyyyMMdd"));
        gfPdf.setCretime(Dates.now());
        gfPdf.setCustomno(gmgfVo.getChoiceCustomNo());
        gfPdf.setOrderno(gmgfVo.getOrderNo());
        gfPdf.setCxflg("0");
        gfPdf.setOwnref(gmgfVo.getBizInfo().getOwnref());
        gfPdf.setJktyp(jktyp);
        gfPdfMapper.insertSelective(gfPdf);
        return gfPdf.getInr();
    }

    /**
     * GE57 报关单参考余额查询
     *
     * @param unionOrgCode 统一社会信用代码
     * @param customNo     报关单号
     * @param cur          币种
     */
    private InnerRes<InnerResGe57> DealGe57Interface(String unionOrgCode, String customNo, String cur) {
        InnerReq<InnerReqGe57> req = new InnerReq<>();
        InnerReqGe57 data = new InnerReqGe57();
        data.customCurrency = cur;
        data.customNo = customNo;
        data.unionOrgCode = unionOrgCode;
        req.DATA = data;
        req.header = new Header();
        return gmgfFeign.callGe57(req);
    }

    /**
     * GE59 报关单信息查询
     *
     * @param unionOrgCode 统一社会信用代码
     * @param orderNo      平台订单编号
     * @param customNo     报关单号
     */
    private InnerRes<InnerResGe59> DealGe59Interface(String unionOrgCode, String orderNo, String customNo) {
        InnerReq<InnerReqGe59> req = new InnerReq<>();
        InnerReqGe59 data = new InnerReqGe59();
        data.unionOrgCode = unionOrgCode;
        data.orderNo = orderNo;
        data.customNo = customNo;
        req.DATA = data;
        req.header = new Header();
        return gmgfFeign.callGe59(req);
    }

    /**
     * GE67 验核报告调阅
     *
     * @param orderNo     订单编号
     * @param verifyrpid  PDF验核报告索引(如果不传值则返回最新的贸易验核报告)
     * @param refreshFlag 更新标识(0 - 不更新（默认）;1 - 强制更新)
     * @param pushFlag    推送标识(0 - 不需要（默认）;1 - 需要（主动推送）)
     * @param inr         主键
     */
    private InnerRes<InnerResGe67> DealGe67Interface(String orderNo, String verifyrpid, String refreshFlag,
                                                     String pushFlag, String inr) {
        InnerReq<InnerReqGe67> req = new InnerReq<>();
        InnerReqGe67 data = new InnerReqGe67();
        data.orderNo = orderNo;
        data.verifyRpId = verifyrpid;
        data.refreshFlag = refreshFlag;
        data.pushFlag = pushFlag;
        data.pdfInr = inr;
        req.DATA = data;
        req.header = new Header();
        InnerRes<InnerResGe67> res = gmgfFeign.callGe67(req);
        assertByCode(res.header.code, "验核报告调阅失败", res.header.description);
        return res;
    }

    /**
     * GE69 报关单影像件调阅
     *
     * @param orderNo  订单编号
     * @param customNo 报关单号
     * @param inr      主键
     */
    private InnerRes<InnerResGe69> DealGe69Interface(String orderNo, String customNo, String inr) {
        InnerReq<InnerReqGe69> req = new InnerReq<>();
        InnerReqGe69 data = new InnerReqGe69();
        data.orderNo = orderNo;
        data.customNo = customNo;
        data.pdfInr = inr;
        req.DATA = data;
        req.header = new Header();
        InnerRes<InnerResGe69> res = gmgfFeign.callGe69(req);
        assertByCode(res.header.code, "报关单影像件调阅失败", res.header.description);
        return res;
    }

    /**
     * 获取当前交易机构信息
     */
    private Pty getPty(String act) {
        PtsptaQueryVo ptsptaVo = new PtsptaQueryVo();
        ptsptaVo.setOrcact(act);
        PtsptaInfoVo ptsptaInfoVo = ptsptaMapper.selectExtkeyByOrcact(ptsptaVo);
        if (Objects.isNull(ptsptaInfoVo)) {
            if (log.isInfoEnabled()) {
                log.info("业务所属客户信息为空.");
            }
            return null;
        }
        String ptyInr = ptsptaInfoVo.getPtyinr();
        Pty pty = ptyCacheService.getPtyById(ptyInr);
        if (Objects.isNull(pty)) {
            if (log.isInfoEnabled()) {
                log.info("pty为空.");
            }
            return null;
        }
        return pty;
    }

    /**
     * 判断调用国贸公服相关接口的状态判断
     *
     * @param code     状态码，不为0表示失败
     * @param desPart1 调用的接口描述
     * @param desPart2 具体错误描述
     */
    private void assertByCode(String code, String desPart1, String desPart2) {
        if (!"0".equals(code)) {
            throw new CommonServiceException(StringUtils.stringAppend(desPart1,
                    "###", desPart2));
        }
    }

    /**
     * 根据JSON对象组类似E结算的报文展示模型数据
     *
     * @param type     展示报文类型
     * @param source   数据源
     * @param despList 待展示的模型数据
     * @param lvl      层级
     */
    private void generateShowModel(String type, LinkedHashMap<String, Object> source,
                                   List<ShowMsg> despList, int lvl) {
        Set<String> dup = new HashSet<>();
        for (String tag : source.keySet()) {
            ShowMsg tagDesp = new ShowMsg();
            Object value = source.get(tag);
            if (lvl == 0) {
                tagDesp.setTag("[TITLE]");
            } else {
                tagDesp.setTag(tag);
            }
            tagDesp.setDesp(convertDesp(type, tag));
            tagDesp.setLvl(lvl);
            if (value instanceof LinkedHashMap) {
                tagDesp.setValue("[Object]");
                despList.add(tagDesp);
                generateShowModel(type, (LinkedHashMap) value, despList, lvl + 1);
            } else if (value instanceof List) {
                tagDesp.setValue("[Object]");
                despList.add(tagDesp);
                List jsonArray = (List) value;
                for (int i = 0; i < jsonArray.size(); i++) {
                    LinkedHashMap jsonObject = (LinkedHashMap) jsonArray.get(i);
                    generateShowModel(type, jsonObject, despList, lvl + 1);
                }
            } else {
                if (!dup.add(tag.toLowerCase())) {
                    //因为上面jackson将bean转source多转类，这里去重
                    continue;
                }
                String valueStr = (String) source.get(tag);
                valueStr = convertDesp(tag, valueStr);
                tagDesp.setValue(valueStr);
                despList.add(tagDesp);
            }
        }
    }

    /**
     * 根据JSON对象组类似E结算的报文展示模型数据
     *
     * @param type     展示报文类型
     * @param json     对象json串
     * @param despList 待展示的模型数据
     * @param lvl      层级
     */
    private void generateShowModelFromJson(String type, JSONObject json,
                                           List<ShowMsg> despList, int lvl) {
        for (String tag : json.keySet()) {
            ShowMsg tagDesp = new ShowMsg();
            Object value = json.get(tag);
            tagDesp.setTag(tag);
            tagDesp.setDesp(convertDesp(type, tag));
            tagDesp.setLvl(lvl);
            if (value instanceof JSONObject) {
                tagDesp.setValue("[Object]");
                despList.add(tagDesp);
                generateShowModelFromJson(type, (JSONObject) value, despList, lvl + 1);
            } else if (value instanceof JSONArray) {
                tagDesp.setValue("[Object]");
                despList.add(tagDesp);
                JSONArray jsonArray = (JSONArray) value;
                for (int i = 0; i < jsonArray.size(); i++) {
                    JSONObject jsonObject = jsonArray.getJSONObject(i);
                    generateShowModelFromJson(type, jsonObject, despList, lvl + 1);
                }
            } else {
                String valueStr = json.getStr(tag);
                valueStr = convertDesp(tag, valueStr);
                tagDesp.setValue(valueStr);
                despList.add(tagDesp);
            }
        }
    }

    private String convertDesp(String type, String tag) {
        Map<String, String> codeTable = config.getBlock(type);
        if (ObjectUtils.isEmpty(codeTable)) {
            return tag;
        }
        String convertTag = codeTable.get(tag);
        return StringUtils.hasText(convertTag) ? convertTag : tag;
    }

    /**
     * 去掉路径最前面的‘/’
     *
     * @param path 文件路径
     */
    private static String cleanPath(String path) {
        path = path.replace("\\", "/");
        if (path.startsWith("/") && path.length() > 1) {
            path = path.substring(1);
        }
        return path;
    }

    /**
     * 根据交易码取对应的收汇款人账号
     */
    private String getAct(AbstractCommonVo vo) {
        String trnName = SettleContext.getDirectTransName();
        String act = null;
        if (trnName.contains("CPTOPN")) {
            act = Utils.getFieldValue(vo, "rec//orcact");
        } else if (trnName.contains("CPTADV")) {
            act = Utils.getFieldValue(vo, "rec//pyeact");
        }
        return act;
    }
}
