package com.njcn.gather.report.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.report.pojo.enums.ItemReportKeyEnum;
import com.njcn.gather.report.pojo.result.SingleTestResult;
import com.njcn.gather.tools.report.util.Docx4jUtil;
import org.docx4j.wml.ObjectFactory;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.docx4j.wml.RPr;
import org.docx4j.wml.Tbl;
import org.docx4j.wml.Tc;
import org.docx4j.wml.TcPr;
import org.docx4j.wml.Text;
import org.docx4j.wml.Tr;
import org.docx4j.wml.TrPr;

import javax.xml.bind.JAXBElement;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * "指标锚点"模板的表格装配器：模板表（末行为 key 行）+ SingleTestResult → 成品表列表。
 * 只服务新路径，不被老路径引用；对 Docx4jUtil 只调用不修改。
 * 设计文档：docs/superpowers/specs/2026-07-02-item-anchor-report-template-design.md §4.2 / §6
 *
 * @author hongawen
 */
public final class ItemAnchorAssembler {

    private ItemAnchorAssembler() {
    }

    /**
     * 构造成品表。results 每个有效元素产出一张表（谐波类调用方按 scriptIndex 拆分后传入）。
     *
     * @param scriptCode 指标编码（P 走有功功率特例映射）
     * @param results    检测结果列表，null/空 detail 的元素跳过
     * @param template   detail 模板池中该指标的 H5 分组（取其中第一张表为模板）
     * @param tableKeys  key 行的占位符列表（调用方经 Docx4jUtil.getTableFillKeys 读出）
     * @param factory    docx4j 工厂
     * @return 待插入锚点的表格元素列表（JAXBElement&lt;Tbl&gt;）
     */
    @SuppressWarnings("unchecked")
    public static List<Object> buildTables(String scriptCode, List<SingleTestResult> results,
                                           Docx4jUtil.HeadingContent template, List<String> tableKeys,
                                           ObjectFactory factory) {
        List<Object> out = new ArrayList<>();
        JAXBElement<Tbl> templateTbl = findFirstTable(template);
        if (templateTbl == null || CollUtil.isEmpty(results) || CollUtil.isEmpty(tableKeys)) {
            return out;
        }
        int tableNo = 0;
        for (SingleTestResult result : results) {
            List<Map<String, String>> rows = flattenRows(result, scriptCode);
            if (rows.isEmpty()) {
                continue;
            }
            tableNo++;
            applyHarmonicDerivedValues(rows, scriptCode);
            Map<String, String> fixedPlaceholders = buildHarmonicFixedPlaceholders(rows, scriptCode);
            JAXBElement<Tbl> copied;
            try {
                copied = Docx4jUtil.deepCopyTbl(templateTbl);
            } catch (Exception e) {
                throw new RuntimeException("指标锚点模板表深拷贝失败: " + scriptCode, e);
            }
            Tbl tbl = copied.getValue();
            replaceFixedPlaceholders(tbl, fixedPlaceholders);
            applyHarmonicPowerHeader(tbl, scriptCode, tableNo, rows);
            List<Object> trs = tbl.getContent();
            // 模板规范约定：最后一行是 key 行（不走 judgeTableCross，新路径自带定位依据）；
            // 从尾部找最后一个真正的 Tr——Word 重存可能把 bookmarkStart/End 漏成 w:tbl 直接子节点（本仓 _GoBack 先例）
            Tr keyRow = findLastRow(trs);
            if (keyRow == null) {
                throw new BusinessException("指标 " + scriptCode + " 的模板表无有效行，请检查 detail 模板该分组表格");
            }
            TrPr trPr = keyRow.getTrPr();
            List<TcPr> tcPrList = new ArrayList<>();
            RPr templateRPr = null;
            for (Object cellObj : keyRow.getContent()) {
                if (cellObj instanceof JAXBElement) {
                    Tc cell = ((JAXBElement<Tc>) cellObj).getValue();
                    TcPr tcPr = cell.getTcPr() != null ? cell.getTcPr() : factory.createTcPr();
                    // 注意：不覆写 tcW（老 fill 的 5000/n 均分会破坏客户模板列宽），沿用模板原宽
                    tcPrList.add(tcPr);
                    if (templateRPr == null && !cell.getContent().isEmpty() && cell.getContent().get(0) instanceof P) {
                        templateRPr = Docx4jUtil.getTcPrFromParagraph((P) cell.getContent().get(0));
                    }
                }
            }
            trs.remove(keyRow);
            for (Map<String, String> row : rows) {
                // 缺失 key 一律补空串："拿不到的先空着"（spec §6 #2）
                for (String key : tableKeys) {
                    row.putIfAbsent(key, "");
                }
                Tr newRow = Docx4jUtil.createCustomRow(factory, row, tableKeys, trPr, tcPrList, templateRPr, true);
                // Docx4jUtil.createCustomRow 产出的单元格是裸 Tc（未包 JAXBElement），
                // 与模板行（真实 docx4j 解析结果 / deepCopy 而来）里统一的 JAXBElement<Tc> 不一致；
                // 这里做一次归一化包装，避免下游按 JAXBElement 读取该行时 ClassCastException（不改 createCustomRow 本身）
                List<Object> normalized = new ArrayList<>();
                for (Object cellObj : newRow.getContent()) {
                    if (cellObj instanceof Tc) {
                        normalized.add(factory.createTrTc((Tc) cellObj));
                    } else {
                        normalized.add(cellObj);
                    }
                }
                newRow.getContent().clear();
                newRow.getContent().addAll(normalized);
                trs.add(newRow);
            }
            out.add(copied);
        }
        // 多表（谐波类多 scriptIndex）背靠背插入时，OOXML 相邻 w:tbl 会被 Word 合并渲染成一张大表；
        // 只插入空段落做结构分隔。测点号属于 HP 表内标题单元格，不能作为表外可见段落。
        if (out.size() > 1) {
            List<Object> withSeparators = new ArrayList<>(out.size() * 2 - 1);
            for (int i = 0; i < out.size(); i++) {
                withSeparators.add(out.get(i));
                if (i < out.size() - 1) {
                    withSeparators.add(factory.createP());
                }
            }
            return withSeparators;
        }
        return out;
    }

    /**
     * 校验模板 key 行占位符已成功取出。调用方仅在"该指标有脚本明细且模板分组存在"时调用：
     * 此时 keys 为空属模板错误（key 行缺失或表结构不符合 getTableFillKeys 的识别规则），
     * 必须显性中断报告生成，不得静默走"未测→整节隐藏"（否则有数据的节凭空消失，违反红线 2）。
     */
    public static void requireTableKeys(String scriptCode, List<String> tableKeys) {
        if (CollUtil.isEmpty(tableKeys)) {
            throw new BusinessException("指标 " + scriptCode + " 的模板表 key 行占位符解析为空，请检查 detail 模板该分组的表格（表末行应为占位符 key 行），已中断报告生成");
        }
    }

    /**
     * 指标锚点模板专用 key 读取：按 spec 直接取 H5 分组第一张表的最后一个真实 Tr。
     * 不复用旧 getTableFillKeys 的横/纵表启发式，避免 HV/HI/HP 标题区末格为数值时被误判。
     */
    public static List<String> extractTableKeys(List<Docx4jUtil.HeadingContent> tempContent) {
        List<String> keys = new ArrayList<>();
        if (CollUtil.isEmpty(tempContent)) {
            return keys;
        }
        JAXBElement<Tbl> table = findFirstTable(tempContent.get(0));
        if (table == null) {
            return keys;
        }
        Tr keyRow = findLastRow(table.getValue().getContent());
        if (keyRow == null) {
            return keys;
        }
        for (Object cellObj : keyRow.getContent()) {
            Object value = cellObj instanceof JAXBElement ? ((JAXBElement<?>) cellObj).getValue() : cellObj;
            if (value instanceof Tc) {
                keys.add(Docx4jUtil.getTextFromCell((Tc) value));
            }
        }
        return keys;
    }

    /** 从尾部向前找最后一个真正的 Tr（含 JAXBElement 解包）；无 Tr 返回 null */
    private static Tr findLastRow(List<Object> content) {
        for (int i = content.size() - 1; i >= 0; i--) {
            Object v = content.get(i) instanceof JAXBElement
                    ? ((JAXBElement<?>) content.get(i)).getValue() : content.get(i);
            if (v instanceof Tr) {
                return (Tr) v;
            }
        }
        return null;
    }

    /**
     * 把 SingleTestResult 的三层嵌套 detail 摊平成行列表，并做新模板专属的行级补充：
     * errorScope 注入（HV/HI/HP 的标准限值列）、time 缺失时"测点{n}"兜底（F/IMBV/IMBA）、P 有功特例。
     */
    static List<Map<String, String>> flattenRows(SingleTestResult result, String scriptCode) {
        List<Map<String, String>> out = new ArrayList<>();
        if (result == null || CollUtil.isEmpty(result.getDetail())) {
            return out;
        }
        int seq = 0;
        for (Map.Entry<String, List<Map<String, List<Map<String, String>>>>> influenceEntry : result.getDetail().entrySet()) {
            if (influenceEntry.getValue() == null) {
                continue;
            }
            for (Map<String, List<Map<String, String>>> byError : influenceEntry.getValue()) {
                if (byError == null) {
                    continue;
                }
                for (Map.Entry<String, List<Map<String, String>>> errorEntry : byError.entrySet()) {
                    String errorScope = errorEntry.getKey();
                    if (errorEntry.getValue() == null) {
                        continue;
                    }
                    for (Map<String, String> raw : errorEntry.getValue()) {
                        seq++;
                        Map<String, String> row = new LinkedHashMap<>(raw);
                        applyErrorScope(row, scriptCode, errorScope);
                        if (StrUtil.isBlank(row.get(ItemReportKeyEnum.TIME.getKey()))) {
                            row.put(ItemReportKeyEnum.TIME.getKey(), "测点" + seq);
                        }
                        if ("P".equalsIgnoreCase(scriptCode)) {
                            applyPowerMapping(row);
                        }
                        out.add(row);
                    }
                }
            }
        }
        return out;
    }

    private static void applyErrorScope(Map<String, String> row, String scriptCode, String errorScope) {
        String key = ItemReportKeyEnum.ERROR_SCOPE.getKey();
        if ("HV".equalsIgnoreCase(scriptCode) || "HP".equalsIgnoreCase(scriptCode)) {
            String value = StrUtil.isBlank(row.get(key)) ? errorScope : row.get(key);
            row.put(key, stripTrailingUnit(value));
            return;
        }
        row.putIfAbsent(key, errorScope);
    }

    private static String stripTrailingUnit(String value) {
        if (StrUtil.isBlank(value)) {
            return value;
        }
        return value.trim().replaceFirst("\\s*(?:(?:[%％‰°℃]|[a-zA-Z]+|[\\u4e00-\\u9fa5]+)\\s*)+$", "");
    }

    /**
     * P（有功功率）特例：activePower = 三相实测之和；设置类与无功/视在/功率因数数据侧暂无，留空。
     * spec §6 #2 处置。
     */
    static void applyPowerMapping(Map<String, String> row) {
        BigDecimal sum = BigDecimal.ZERO;
        boolean hasValue = false;
        for (String key : Arrays.asList(ItemReportKeyEnum.TEST_A.getKey(),
                ItemReportKeyEnum.TEST_B.getKey(), ItemReportKeyEnum.TEST_C.getKey())) {
            String value = row.get(key);
            if (StrUtil.isNotBlank(value)) {
                try {
                    sum = sum.add(new BigDecimal(value.trim()));
                    hasValue = true;
                } catch (NumberFormatException ignored) {
                    // 非数值（如"--"）不参与求和
                }
            }
        }
        String activePower = row.get(ItemReportKeyEnum.ACTIVE_POWER.getKey());
        if (hasValue && (StrUtil.isBlank(activePower) || "/".equals(activePower))) {
            row.put(ItemReportKeyEnum.ACTIVE_POWER.getKey(), sum.toPlainString());
        }
    }

    private static void applyHarmonicDerivedValues(List<Map<String, String>> rows, String scriptCode) {
        if (!"HI".equalsIgnoreCase(scriptCode)) {
            return;
        }
        for (Map<String, String> row : rows) {
            String scriptStandardRate = row.get(ItemReportKeyEnum.STANDARD_RATE.getKey());
            String standardRate = parseNumber(scriptStandardRate) == null ? "" : scriptStandardRate;
            String testRateA = rate(row.get(ItemReportKeyEnum.TEST_A.getKey()), row.get(ItemReportKeyEnum.BASE_CURRENT_A.getKey()));
            String testRateB = rate(row.get(ItemReportKeyEnum.TEST_B.getKey()), row.get(ItemReportKeyEnum.BASE_CURRENT_B.getKey()));
            String testRateC = rate(row.get(ItemReportKeyEnum.TEST_C.getKey()), row.get(ItemReportKeyEnum.BASE_CURRENT_C.getKey()));
            String errorRateA = difference(testRateA, standardRate);
            String errorRateB = difference(testRateB, standardRate);
            String errorRateC = difference(testRateC, standardRate);
            row.put(ItemReportKeyEnum.STANDARD_RATE.getKey(), standardRate);
            row.put(ItemReportKeyEnum.TEST_RATE_A.getKey(), testRateA);
            row.put(ItemReportKeyEnum.TEST_RATE_B.getKey(), testRateB);
            row.put(ItemReportKeyEnum.TEST_RATE_C.getKey(), testRateC);
            row.put(ItemReportKeyEnum.ERROR_RATE_A.getKey(), errorRateA);
            row.put(ItemReportKeyEnum.ERROR_RATE_B.getKey(), errorRateB);
            row.put(ItemReportKeyEnum.ERROR_RATE_C.getKey(), errorRateC);
            row.put(ItemReportKeyEnum.ERROR_SCOPE.getKey(), harmonicCurrentErrorScope(standardRate));
        }
    }

    private static String harmonicCurrentErrorScope(String standardRateText) {
        Double standardRate = parseNumber(standardRateText);
        if (standardRate == null) {
            return "";
        }
        double limit = standardRate >= 3D ? standardRate * 0.05D : 0.15D;
        return "±" + format4(limit);
    }

    private static Map<String, String> buildHarmonicFixedPlaceholders(List<Map<String, String>> rows, String scriptCode) {
        Map<String, String> placeholders = new LinkedHashMap<>();
        if ("HV".equalsIgnoreCase(scriptCode)) {
            placeholders.put(ItemReportKeyEnum.STANDARD_THD.getKey(), thd(rows, ItemReportKeyEnum.STANDARD.getKey()));
            placeholders.put(ItemReportKeyEnum.THD_A.getKey(), thd(rows, ItemReportKeyEnum.TEST_A.getKey()));
            placeholders.put(ItemReportKeyEnum.THD_B.getKey(), thd(rows, ItemReportKeyEnum.TEST_B.getKey()));
            placeholders.put(ItemReportKeyEnum.THD_C.getKey(), thd(rows, ItemReportKeyEnum.TEST_C.getKey()));
        } else if ("HI".equalsIgnoreCase(scriptCode)) {
            placeholders.put(ItemReportKeyEnum.STANDARD_THD.getKey(), thd(rows, ItemReportKeyEnum.STANDARD_RATE.getKey()));
            placeholders.put(ItemReportKeyEnum.THD_A.getKey(), thd(rows, ItemReportKeyEnum.TEST_RATE_A.getKey()));
            placeholders.put(ItemReportKeyEnum.THD_B.getKey(), thd(rows, ItemReportKeyEnum.TEST_RATE_B.getKey()));
            placeholders.put(ItemReportKeyEnum.THD_C.getKey(), thd(rows, ItemReportKeyEnum.TEST_RATE_C.getKey()));
        }
        return placeholders;
    }

    private static void applyHarmonicPowerHeader(Tbl tbl, String scriptCode, int pointNo, List<Map<String, String>> rows) {
        if (!"HP".equalsIgnoreCase(scriptCode)) {
            return;
        }
        Tr firstRow = findFirstRow(tbl.getContent());
        if (firstRow == null) {
            return;
        }
        Tc titleCell = cellAt(firstRow, 0);
        if (titleCell == null) {
            return;
        }
        String title = Docx4jUtil.getTextFromCell(titleCell);
        String baseTitle = StrUtil.blankToDefault(title, StrUtil.EMPTY)
                .replaceFirst("（测点\\s*\\d+）$", "");
        setCellText(titleCell, baseTitle + "（测点" + pointNo + "）");
        fillNextCellByLabel(tbl, "基波电压", firstDataValue(rows, ItemReportKeyEnum.STANDARD_V.getKey()));
        fillNextCellByLabel(tbl, "基波电流", firstDataValue(rows, ItemReportKeyEnum.STANDARD_I.getKey()));
    }

    private static String firstDataValue(List<Map<String, String>> rows, String key) {
        if (CollUtil.isEmpty(rows)) {
            return "";
        }
        for (Map<String, String> row : rows) {
            String value = row.get(key);
            if (StrUtil.isNotBlank(value) && !"/".equals(value.trim())) {
                return value;
            }
        }
        return "";
    }

    private static void fillNextCellByLabel(Tbl tbl, String label, String value) {
        if (StrUtil.isBlank(value) || tbl == null || tbl.getContent() == null) {
            return;
        }
        for (Object o : tbl.getContent()) {
            Object v = o instanceof JAXBElement ? ((JAXBElement<?>) o).getValue() : o;
            if (v instanceof Tr && fillNextCellByLabel((Tr) v, label, value)) {
                return;
            }
        }
    }

    private static boolean fillNextCellByLabel(Tr row, String label, String value) {
        if (StrUtil.isBlank(value) || row == null || row.getContent() == null) {
            return false;
        }
        List<Object> cells = row.getContent();
        for (int i = 0; i < cells.size() - 1; i++) {
            Tc labelCell = cellAt(row, i);
            if (labelCell != null && Docx4jUtil.getTextFromCell(labelCell).contains(label)) {
                Tc valueCell = cellAt(row, i + 1);
                if (valueCell != null) {
                    setCellText(valueCell, value);
                }
                return true;
            }
        }
        return false;
    }

    private static Tr findFirstRow(List<Object> content) {
        for (Object o : content) {
            Object v = o instanceof JAXBElement ? ((JAXBElement<?>) o).getValue() : o;
            if (v instanceof Tr) {
                return (Tr) v;
            }
        }
        return null;
    }

    private static Tc cellAt(Tr row, int index) {
        if (row == null || row.getContent() == null || row.getContent().size() <= index) {
            return null;
        }
        Object cellObj = row.getContent().get(index);
        Object value = cellObj instanceof JAXBElement ? ((JAXBElement<?>) cellObj).getValue() : cellObj;
        return value instanceof Tc ? (Tc) value : null;
    }

    private static String rate(String numeratorText, String denominatorText) {
        Double numerator = parseNumber(numeratorText);
        Double denominator = parseNumber(denominatorText);
        if (numerator == null || denominator == null || denominator == 0D) {
            return "";
        }
        return format4(numerator / denominator * 100D);
    }

    private static String difference(String minuendText, String subtrahendText) {
        Double minuend = parseNumber(minuendText);
        Double subtrahend = parseNumber(subtrahendText);
        if (minuend == null || subtrahend == null) {
            return "";
        }
        return format4(minuend - subtrahend);
    }

    private static String thd(List<Map<String, String>> rows, String key) {
        double sum = 0D;
        boolean hasValue = false;
        for (Map<String, String> row : rows) {
            Double value = parseNumber(row.get(key));
            if (value != null) {
                sum += value * value;
                hasValue = true;
            }
        }
        return hasValue ? format4(Math.sqrt(sum)) : "";
    }

    private static Double parseNumber(String text) {
        if (StrUtil.isBlank(text) || "/".equals(text.trim()) || "--".equals(text.trim())) {
            return null;
        }
        try {
            return Double.parseDouble(text.trim());
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    private static String format4(double value) {
        return String.format(Locale.ROOT, "%.4f", value);
    }

    private static void replaceFixedPlaceholders(Tbl tbl, Map<String, String> values) {
        if (values.isEmpty()) {
            return;
        }
        for (Object rowObj : tbl.getContent()) {
            Object rowValue = rowObj instanceof JAXBElement ? ((JAXBElement<?>) rowObj).getValue() : rowObj;
            if (!(rowValue instanceof Tr)) {
                continue;
            }
            for (Object cellObj : ((Tr) rowValue).getContent()) {
                Object cellValue = cellObj instanceof JAXBElement ? ((JAXBElement<?>) cellObj).getValue() : cellObj;
                if (cellValue instanceof Tc) {
                    Tc cell = (Tc) cellValue;
                    String replacement = values.get(Docx4jUtil.getTextFromCell(cell));
                    if (replacement != null) {
                        setCellText(cell, replacement);
                    }
                }
            }
        }
    }

    private static void setCellText(Tc cell, String value) {
        P firstParagraph = null;
        for (Object content : cell.getContent()) {
            if (content instanceof P) {
                firstParagraph = (P) content;
                break;
            }
        }
        if (firstParagraph == null) {
            firstParagraph = new P();
            cell.getContent().add(firstParagraph);
        }
        boolean written = false;
        for (Object paragraphObj : cell.getContent()) {
            if (!(paragraphObj instanceof P)) {
                continue;
            }
            P paragraph = (P) paragraphObj;
            for (Object runObj : paragraph.getContent()) {
                if (!(runObj instanceof R)) {
                    continue;
                }
                R run = (R) runObj;
                for (Object textObj : run.getContent()) {
                    Text text = unwrapText(textObj);
                    if (text != null) {
                        text.setValue(written ? "" : value);
                        written = true;
                    }
                }
            }
        }
        if (!written) {
            R run = new R();
            Text text = new Text();
            text.setValue(value);
            run.getContent().add(text);
            firstParagraph.getContent().add(run);
        }
    }

    private static Text unwrapText(Object textObj) {
        if (textObj instanceof Text) {
            return (Text) textObj;
        }
        if (textObj instanceof JAXBElement && ((JAXBElement<?>) textObj).getValue() instanceof Text) {
            return (Text) ((JAXBElement<?>) textObj).getValue();
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private static JAXBElement<Tbl> findFirstTable(Docx4jUtil.HeadingContent template) {
        if (template == null || CollUtil.isEmpty(template.getSubContent())) {
            return null;
        }
        for (Object o : template.getSubContent()) {
            if (o instanceof JAXBElement && ((JAXBElement<?>) o).getValue() instanceof Tbl) {
                return (JAXBElement<Tbl>) o;
            }
        }
        return null;
    }
}
