package com.brilliance.isc.utils;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.brilliance.isc.common.transaction.vo.BaseTransactionVo;
import com.brilliance.isc.constant.ErrorCode;
import com.brilliance.isc.exception.RestBizException;
import com.brilliance.mda.runtime.mda.util.MdaUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

public class ObjectUtils {

    private static final String REGEX_CUR_AMT = "((?:AUD|CAD|CHF|CNY|DKK|EUR|FIM|FRF|GBP|HKD|JPY|KRW|MOP|NOK|NZD|SEK|SGD|THB|TWD|USD)\\d+(?:,\\d{3})*(?:\\.\\d{2})?)";
    private static final String REGEX_PERCENTAGE_DATA = "(\\d+(?:\\.\\d{1,2})?%)";
    private static Logger log = LoggerFactory.getLogger(ObjectUtils.class);

    public static Map<String, Object> dataToMap(Object dataBo) {
        try {
            String json = MdaUtils.toJson(dataBo);
            Map<String, Object> dataMap = JSONObject.parseObject(json, Map.class);
            //展开Map
            Map<String, Object> resultMap = new HashMap<>();
            flatMap(dataMap, resultMap, "");
            return resultMap;
        } catch (Exception e) {
            log.error("dataToMap 数据转换失败：{}", e);
        }
        return new HashMap<>();
    }

    public static Map<String, Object> dataToMapOnlyForGidTxt(Object dataBo, Locale locale) {
        try {
            String json = MdaUtils.toJsonForGidTxt(dataBo, locale);
            Map<String, Object> dataMap = JSONObject.parseObject(json, Map.class);
            //展开Map
            Map<String, Object> resultMap = new HashMap<>();
            flatMap(dataMap, resultMap, "");
            return resultMap;
        } catch (Exception e) {
            log.error("dataToMap 数据转换失败：{}", e);
        }
        return new HashMap<>();
    }

    private static void flatMap(Map<String, Object> dataMap, Map<String, Object> result, String prefix) {
        for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
            Object value = entry.getValue();
            String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
            if (value instanceof Map) {
                flatMap((Map) value, result, key);
            } else {
                result.put(key, value);
            }
        }
    }

    public static String exchangeTemplateVar(String templateStr, Map<String, String> fieldMap, BaseTransactionVo bo, Map<String, Object> extraMap) {
        Locale locale = Locale.ENGLISH;
        //国内交易，进入中文模式，目前主要影响日期
        if ("nitopn-nitpop-hitopn-hitpop".indexOf(bo.getTransName().toLowerCase()) > -1) {
            String giduil = getFieldValue(bo, "nidgrp//rec//giduil");
            if (!"en".equalsIgnoreCase(giduil)) {
                locale = Locale.CHINESE;
            }
        }
        Map<String, Object> dataMap = ObjectUtils.dataToMapOnlyForGidTxt(bo,locale);
        Map<String, Object> reflectMap = new HashMap<>();
        fieldMap.forEach((var, path) -> {
            if (var.startsWith("#")) {
                //#表示显示原数据即可
                reflectMap.put(var.substring(1), path);
                reflectMap.put(var.substring(1).toLowerCase(), path);
                reflectMap.put(var.substring(1).toUpperCase(), path);
                return;
            }
            path = path.toLowerCase().replaceAll("\\\\{1,2}", ".");
            Object val = null;
            if(extraMap !=null && extraMap.containsKey(path)){
                val = extraMap.get(path);
                if (val == null) {
                    val = "";
                }
            } else if (dataMap.containsKey(path)) {
                val = dataMap.get(path);
                if (val == null) {
                    val = "";
                }
            } else {
                val = "unkonw var " + var;
            }
            reflectMap.put(var.toLowerCase(), val);
            reflectMap.put(var.toUpperCase(), val);
            reflectMap.put(var, val);
        });

        Iterator<Map.Entry<String, String>> iterator = fieldMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<String, String> next = iterator.next();
            if (reflectMap.containsKey(next.getKey())){
                iterator.remove();
            }
        }
        log.info("保函文本未获取到值的变量:{}", JSON.toJSONString(iterator));


        if (extraMap != null) {
            reflectMap.putAll(extraMap);
        }
        //将模板中的变量替换为${var}这种格式
        templateStr = templateStr.replaceAll("\\{(\\w+)\\}", "\\${$1!'unkown $1'}");
        Configuration configuration = new Configuration();
        configuration.setTemplateLoader(new StringTemplateLoader());
        configuration.setDefaultEncoding("utf-8");
        try (StringWriter writer = new StringWriter();) {
            Template template = new Template("txt", templateStr, configuration);
            template.process(reflectMap, writer);
            return writer.toString();
        } catch (IOException | TemplateException e) {
            log.error("生成文本发生异常:{}", e);
            throw new RestBizException(ErrorCode.MT_MESSAGE_BUILD_ERROR, "生成文本发生异常");
        }

    }

    public static String exchangeTemplateVarExceType(String templateStr, Map<String, String> fieldMap, BaseTransactionVo bo, Map<String, Object> extraMap) {
        Map<String, Object> dataMap = ObjectUtils.dataToMap(bo);
        Map<String, Object> reflectMap = new HashMap<>();
        fieldMap.forEach((var, path) -> {
            if (var.startsWith("#")) {
                //#表示显示原数据即可
                reflectMap.put(var.substring(1), path);
                return;
            }
            path = path.toLowerCase().replaceAll("\\\\{1,2}", ".");
            Object val = null;
            if (dataMap.containsKey(path)) {
                val = dataMap.get(path);
                if (val == null) {
                    val = "";
                }
            } else {
                val = "unkonw var " + var;
            }
            reflectMap.put(var, val);
        });
        // 新增逻辑，如果是国内保函的转开或者反担保或者直开，包含文本中担保行选取当前的登录机构
        reflectMap.remove("GUARANTORCN");
        reflectMap.put("GUARANTORCN", dataMap.get("nidgrp.rec.dbyhmc").toString());
        if (extraMap != null) {
            reflectMap.putAll(extraMap);
        }
        //将模板中的变量替换为${var}这种格式
        templateStr = templateStr.replaceAll("\\{(\\w+)\\}", "\\${$1!'unkown $1'}");
        Configuration configuration = new Configuration();
        configuration.setTemplateLoader(new StringTemplateLoader());
        configuration.setDefaultEncoding("utf-8");
        try (StringWriter writer = new StringWriter();) {
            Template template = new Template("txt", templateStr, configuration);
            template.process(reflectMap, writer);
            return writer.toString();
        } catch (IOException | TemplateException e) {
            log.error("生成文本发生异常:{}", e);
            throw new RestBizException(ErrorCode.MT_MESSAGE_BUILD_ERROR, "生成文本发生异常");
        }

    }

    public static Object invokeGetMethod(Object obj, String fieldName) {
        String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        try {
            Class<?> clazz = obj.getClass();
            Method method = clazz.getDeclaredMethod(methodName, null);
            if (method != null) {
                return method.invoke(obj, null);
            }
        } catch (Exception e) {
            //throw new CommonServiceException(e.getMessage());
            return null;
        }
        return null;
    }

    public static <T> T getFieldValue(Object obj, String fieldName) {
        if (obj == null || StringUtils.isEmpty(fieldName)) {
            return null;
        }
        T getObj = (T) obj;
        String[] paths = fieldName.split("//");
        for (int i = 0; i < paths.length; i++) {
            getObj = (T) invokeGetMethod(getObj, paths[i]);
        }
        return getObj;
    }

    public static String replaceGidTextSpace(String htmlStr){
        //处理保函文本空行
        htmlStr = htmlStr.replaceAll("(?<!\\>)<br/?></","<br/>&#12288;</");
        htmlStr = htmlStr.replaceAll("<br/?><br/?>"," <br/>&#12288;<br/>");
        htmlStr = htmlStr.replaceAll("> <",">&#12288;<");
        htmlStr = htmlStr.replaceAll("&nbsp; ","&#12288;").replaceAll("&nbsp;&nbsp;","&#12288;");
        htmlStr = htmlStr.replaceAll("&nbsp;","&#12288;");
        return htmlStr;
    }

    public static void distinguishOAndZero(Map<String, Object> extraMap, String key) {
        if (extraMap != null && extraMap.containsKey(key)) {
            String newGidtxt = ((String) extraMap.get(key)).replaceAll("O(?!(F|CTYPE|</span>))", "<span style=\"font-family: Arial;\">O</span>");
            extraMap.put(key, newGidtxt);
        }
    }

    public static void setEnglishAndNumberFont(Map map, String fontName) {
        String fontFamily = String.format("<span style=\"font-family: %s;\">$0</span>", null == fontName ? "Arial" : fontName);
        if (map != null) {
            for (Object key : map.keySet()) {
                Object value = map.get(key);
                if (value instanceof String) {
                    String str = (String) value;
                    str = str.replaceAll("[a-zA-Z]*\\d*(,\\d{3})*\\.?\\d*[a-zA-Z0-9]+", fontFamily);
                    map.put(key, str);
                }
            }
        }
    }
    public static String setEnglishAndNumberFont(String text, String fontName) {
        String fontFamily = String.format("<span style=\"font-family: %s;\">$0</span>", null == fontName ? "Arial" : fontName);
        if (text != null) {
            text = text.replaceAll("[a-zA-Z]*\\d*(,\\d{3})*\\.?\\d*[a-zA-Z0-9]+", fontFamily);
        }
        return text;
    }
    /**
     * HTML中英文和数字文本设置字体
     * 遍历处理p,td标签（span暂未处理）
     * @param html   HTML
     * @param fontName 字体名称,为null默认Arial
     * @return
     */
    public static String setHtmlEnglishAndNumberFont(String html, String fontName) {
        String fontFamily = String.format("<span style=\"font-family: %s;\">$0</span>", null == fontName ? "Arial" : fontName);
        if (html != null) {
            html = html.replaceAll("<span style=\"font-family: Arial;\">(O+)</span>", "$1").replace("<br/>", "换行符");
            Document doc = Jsoup.parse(html);
            //段落中英文字母数字及千分位
            Elements paragraphs = doc.select("p");
            //表格中英文字母数字及中划线
            Elements tds = doc.select("td");
            //jsoup规则会在解析后去掉img结束标签，需手动补上
            Elements imgs = doc.select("img");
            //Elements spans = doc.select("span");
            for (Element pElement : paragraphs) {
                String text = pElement.text();
                //标签内无字体设置且内部无span标签(避免覆盖编辑器的设置）
                if (null != text && !pElement.attributes().get("style").contains("font-family") && pElement.select("span").isEmpty()) {
                    text = text.replaceAll("[a-zA-Z]*\\d*(,\\d{3})*\\.?\\d*[a-zA-Z0-9%]+", fontFamily);
                    text = text.replaceAll(REGEX_PERCENTAGE_DATA, "<span style=\"display: inline-block;text-indent: 0;\">$0</span>");
                    pElement.html(text);
                }
            }
            for (Element td : tds) {
                String text = td.text();
                //独立标签且非开立保函文本class=gidtxt
                if (StringUtils.isNotBlank(text) && td.childNodeSize() == 1 && !td.attributes().get("class").contains("gidtxt")) {
                    text = text.replaceAll("[a-zA-Z0-9\\-]*\\d*(,\\d{3})*\\.?\\d*[a-zA-Z0-9]+", fontFamily);
                    td.html(text);
                }
            }
            //无法添加没内容的结束标签，后续替换这个空的
            for (Element img : imgs) {
                img.append("<img src=\"data:image\">");
            }
            html = doc.html();
            //jsoup处理后部分标签需要修正
            html = html.replace("<!doctype html>", "<!DOCTYPE html>")
                    .replace("<meta charset=\"UTF-8\">", "<meta charset=\"UTF-8\"/>")
                    .replace("换行符", "<br/>")
                    //修复img没有结束标签
                    .replace("<img src=\"data:image\">", "");
        }
        return html;
    }
    /**
     * 设置html文本不跨行，自动换行；默认处理保函文本币种+金额
     * 使用word-break时不适用例如CNY123, 456,789整体换行(单独处理数字可以)
     * 这里使用display: inline-block(itext5 td标签中该样式无效，需要依赖宽度自调节）
     * @param html
     * @return
     */
    public static String setHtmlNonBreak(String html, String regex) {
        if (null == html) {
            return html;
        }
        return html.replaceAll(regex == null ? REGEX_CUR_AMT : regex, "<span style=\"display: inline-block;text-indent: 0;\">$0</span>");
    }

    public static String formatPlainText(String text, String fontName) {
        if (null == text) {
            return text;
        }
        text = text.replace("\n", "换行");
        //格式化英文数字默认Arial字体
        text = setEnglishAndNumberFont(text, fontName);
        String[] split = text.split("(?=<span|</span>)");
        StringBuffer sb = new StringBuffer();
        for (String child : split) {
            if (child.startsWith("<span")) {
                sb.append(child);
            } else if (child.startsWith("</span>")) {
                sb.append("</span>");
                sb.append(HtmlEscapeUtils.escapeHtml(child.substring(7)));
            } else {
                sb.append(HtmlEscapeUtils.escapeHtml(child));
            }
        }
        text = sb.toString().replace("换行", "<br/>");
        return text;
    }    /**
     * 将对象转换为另一个对象
     *
     * @param sourObject
     * @param clazz
     * @return
     */
    public static Object cashObject(Object sourObject, Class clazz) throws Exception {

        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String json = objectMapper.writeValueAsString(sourObject);
            log.info("--------------------------当前处理报文为{}",json);
            objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES,true);
            return objectMapper.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            log.error("转换失败", e);
            throw new Exception(e);
        }
    }
    /**
     *
     * @param obj
     * @param fieldName
     * @return
     */
    public static Object invokeFeild(Object obj, String fieldName) {
        try {
            Class<?> clazz = obj.getClass();
            Field feild=clazz.getField(fieldName);
            if(feild!=null) {
                return feild.get(obj);
            }
        } catch (Exception e) {
            //throw new CommonServiceException(e.getMessage());
            return null;
        }
        return null;
    }

    /**
     * 通过反射 得到对象属性值 (多层用//表示)
     *
     * @param obj
     * @param fieldName
     * @return
     */
    public static <T> T fieldValue(Object obj, String fieldName) {
        if (obj == null || StringUtils.isEmpty(fieldName)) {
            return null;
        }
        T getObj = (T) obj;
        String[] paths = fieldName.split("//");
        for (int i = 0; i < paths.length; i++) {
            getObj = (T) invokeFeild(getObj, paths[i]);
        }
        return getObj;
    }

    public static <T> void setFieldValue(T obj, String fieldName, Object value) {
        if (obj == null || StringUtils.isEmpty(fieldName) || value == null) {
            return;
        }
        try {
            Class<?> objClass = obj.getClass();
            Field field = objClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, value);
        } catch (Exception e) {
        }
    }

    /**
     * 根据映射路径给MAP对象赋值
     *
     * @param modsmap
     * @param modpath
     * @param regex
     * @param val
     */
    public static void mappingMods(Map<String, Object> modsmap, String modpath, String regex, Object val) {
        Map<String, Object> curmap = modsmap;
        String[] paths = modpath.split(regex);
        for (int i = 0; i < paths.length; i++) {
            if (i == paths.length - 1) {
                curmap.put(paths[i], val);
            } else {
                Map<String, Object> newcurmap = (Map<String, Object>) curmap.get(paths[i]);
                if (newcurmap == null) {
                    newcurmap = new HashMap<>(16);
                    curmap.put(paths[i], newcurmap);
                }
                curmap = newcurmap;
            }
        }
    }}
