package com.yaoyicloud.render;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yaoyicloud.config.FilerepoProperties;
import com.yaoyicloud.message.FxyProtos;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import com.deepoove.poi.policy.RenderPolicy;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.TableTools;
import com.google.protobuf.util.JsonFormat;
import com.yaoyicloud.message.FxyProtos.FinancialInfo;
import com.yaoyicloud.tools.Util;

/**
 * FinancialInfo渲染器
 *
 */
@Slf4j
public final class FinancialInfoRender extends AbstractRender {
    private final FilerepoProperties filerepoProperties;
    public FinancialInfoRender(String cwd, FilerepoProperties filerepoProperties, FilerepoProperties filerepoProperties1) {
        super(cwd);
        this.filerepoProperties = filerepoProperties1;
    }
    @Override
    protected String getBasicPath() throws IOException {
        return filerepoProperties.getBasePath();
    }

    @Override
    protected String getReportImagePath() {
        return filerepoProperties.getReportImagePath();
    }

    /**
     * Docx 渲染
     *
     * @param info 数据
     * @param templateFileContent 模板内容
     * @return 本地文件目录
     * @throws IOException
     */

    public String renderDocx(String info, Map<String, Object> addtionalMap, byte[] templateFileContent, String relationId) throws IOException {
        log.info("开始渲染附件模块，relationId: {}", relationId);

        // 配置POI-TL渲染器
        ConfigureBuilder builder = Configure.builder();
        RenderPolicy indicatorsRenderPolicy = this.indicatorsRenderPolicy();
        builder.bind("basicInfoChecks", indicatorsRenderPolicy);
        FxyProtos.BasicInfo.Builder basicInfoBuilder = FxyProtos.BasicInfo.newBuilder();
        JsonFormat.parser().merge(info, basicInfoBuilder);

        FxyProtos.BasicInfo defaultInstance = FxyProtos.BasicInfo.getDefaultInstance();
        FxyProtos.BasicInfo mergedProto = defaultInstance.toBuilder()
                .mergeFrom(basicInfoBuilder.build())
                .build();

        String completeJson = JsonFormat.printer()
                .includingDefaultValueFields()
                .print(mergedProto);
        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> data = objectMapper.readValue(completeJson, new TypeReference<Map<String, Object>>() {});
        data.replaceAll((k, v) -> v.equals("") ? "-" : v);

        if (addtionalMap != null) {
            data.putAll(addtionalMap);
        }
        fillBasicDefaultValues(data);

        try {
            // 渲染文档
            String resultPath = this.renderDocx(data, templateFileContent, builder, relationId);
            log.info("渲染附件模块成功，文件路径: {}", resultPath);
            return resultPath;
        } catch (Exception e) {
            log.error("渲染附件模块失败，relationId: {}", relationId, e);
            throw new IOException("文档渲染失败", e);
        }
    }
    /**
     * 填充默认值，确保所有必要字段都存在
     */
    private void fillBasicDefaultValues(Map<String, Object> data) {
        Map<String, Object> basicInfoSummary = (Map<String, Object>) data.get("basicInfoSummary");
        basicInfoSummary.replaceAll((k, v) -> v.equals("") ? "-" : v);
        Map<String, Object> platformExt = (Map<String, Object>) data.get("platformExt");
        platformExt.replaceAll((k, v) -> v.equals("") ? "-" : v);
        Object certReceived = data.get("certReceived");
        if (certReceived.equals("-")) {
            data.put("certReceived", "否");
        }
        Object bankLicense = data.get("bankLicense");
        if (bankLicense.equals("-")) {
            data.put("bankLicense", "否");
        }
    }

//    public String renderDocx(FinancialInfo info, Map<String, Object> addtionalMap, byte[] templateFileContent) throws IOException {
//
//        // 不需要定制展示逻辑的时候，使用protobuf的转json方法
//        String jsonStr = JsonFormat.printer().print(info);
//
//        // 注： 报告模板的模板变量按照json序列化的结果命名
//        // 注： 目前的实现假设：一个session对应一个cwd目录
//        ConfigureBuilder builder = Configure.builder();
//        builder.bind("indicators", new LoopRowIncludeStatisticsTableRenderPolicy("values"));
//        builder.bind("financialDataSeq", new LoopColumnStaticTableRenderPolicy("[", "]", false, true, 2));
//        builder.bind("financialCheckDetails", new LoopRowCutAndMergeFirstColTableRenderPolicy());
//        // 注意使用了SpringEL之后，每个模板变量都要设置值，不然会报错
//        builder.useSpringEL();
//
//        this.docxResultPath = this.renderDocx(jsonStr, addtionalMap, templateFileContent, builder,
//            Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
//        return this.docxResultPath;
//    }

    /**
     * 这些render policy类都应当是共享的 重要设计假设: data的类型cast都可以建立在json通用反序列化后的基本类型基础上。
     */
    public class LoopColumnStaticTableRenderPolicy implements RenderPolicy {

        private String prefix;
        private String suffix;
        private boolean onSameLine;
        private boolean reverse;
        private int valColIndex;

        public LoopColumnStaticTableRenderPolicy() {
            this(false);
        }

        public LoopColumnStaticTableRenderPolicy(boolean onSameLine) {
            this("[", "]", onSameLine);
        }

        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix) {
            this(prefix, suffix, false);
        }

        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine) {
            this(prefix, suffix, onSameLine, false);
        }

        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse) {
            this(prefix, suffix, onSameLine, false, 1);
        }

        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse,
            int valRowIndex) {
            this.prefix = prefix;
            this.suffix = suffix;
            this.onSameLine = onSameLine;
            this.reverse = reverse;
            this.valColIndex = valRowIndex;
        }

        @Override
        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
            RunTemplate runTemplate = (RunTemplate) eleTemplate;
            XWPFRun run = runTemplate.getRun();
            try {
                if (!TableTools.isInsideTable(run)) {
                    throw new IllegalStateException(
                        "The template tag " + runTemplate.getSource() + " must be inside a table");
                }
                XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
                XWPFTable table = tagCell.getTableRow().getTable();
                run.setText("", 0);

                int templateColIndex = getTemplateColIndex(tagCell);
                // 模版变量列总是写在左边
                int minIndex = templateColIndex;
                int maxIndex = table.getRows().get(valColIndex).getTableCells().size() - 1;
                int currIndex = reverse ? maxIndex : minIndex;
                int indexDelta = reverse ? -1 : 1;

                // 目前expression当作数据Map的key来对待，将来可以当作POI-TL变量一致化的处理
                Map<Integer, String> idx2Expression = new HashMap<>();
                for (int i = 0; i < table.getRows().size(); i++) {
                    XWPFTableCell cell = table.getRows().get(i).getCell(templateColIndex);
                    String text = cell.getText().trim();
                    if (text.startsWith(prefix) && text.endsWith(suffix)) {
                        idx2Expression.put(i, text.substring(1, text.length() - 1));
                        cell.setText("");
                    }
                }

                int rowSize = table.getRows().size();
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;

                for (Map<String, Object> realData : mpData) {
                    for (int i = 0; i < rowSize; i++) {
                        if (!idx2Expression.containsKey(i)) {
                            continue;
                        }
                        XWPFTableRow row = table.getRow(i);
                        XWPFTableCell valueCell = row.getCell(currIndex);
                        String valStr = realData.getOrDefault(idx2Expression.get(i), "-").toString();
                        valueCell.setText(valStr);
                    }
                    currIndex += indexDelta;
                }

            } catch (Exception e) {
                throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
            }
        }

        private int getTemplateColIndex(XWPFTableCell tagCell) {
            return onSameLine ? Util.getColIndexOfFirstRow(tagCell) : (Util.getColIndexOfFirstRow(tagCell) + 1);
        }
    }

    public class LoopRowCutAndMergeFirstColTableRenderPolicy extends LoopRowTableRenderPolicy {

        @Override
        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
            RunTemplate runTemplate = (RunTemplate) eleTemplate;
            XWPFRun run = runTemplate.getRun();
            XWPFTable table = null;
            try {
                if (!TableTools.isInsideTable(run)) {
                    throw new IllegalStateException(
                        "The template tag " + runTemplate.getSource() + " must be inside a table");
                }
                // Reserve the first two rows
                XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
                table = tagCell.getTableRow().getTable();
                for (int i = table.getNumberOfRows() - 1; i > 1; i--) {
                    table.removeRow(i);
                }
            } catch (Exception e) {
                throw new RenderException(
                    "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
            }

            // in case data not sorted by rank
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
            mpData.sort((a, b) -> Integer.valueOf(a.getOrDefault("rank", "0").toString())
                - Integer.valueOf(b.getOrDefault("rank", "0").toString()));

            super.render(eleTemplate, data, template);

            try {
                // merge the first column
                Util.mergeFirstNColSimple(table, 1, 0);
            } catch (Exception e) {
                throw new RenderException(
                    "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
            }
        }
    }

    public class LoopRowIncludeStatisticsTableRenderPolicy extends LoopRowTableRenderPolicy {

        private String valueTag;

        public LoopRowIncludeStatisticsTableRenderPolicy(String valueTag) {
            this.valueTag = valueTag;
        }

        @Override
        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
            for (Map<String, Object> row : mpData) {
                @SuppressWarnings("unchecked")
                List<String> values = (List<String>) row.get(valueTag);
                row.put("avg", values.stream().mapToLong(Long::valueOf).average().orElse(Double.NaN));
            }

            super.render(eleTemplate, data, template);
        }
    }
}
