package com.njcn.gather.result.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.njcn.gather.detection.pojo.vo.DetectionData;
import com.njcn.gather.report.pojo.enums.ItemReportKeyEnum;
import com.njcn.gather.script.pojo.po.PqScriptDtls;
import com.njcn.gather.storage.pojo.po.SimAndDigNonHarmonicResult;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
 * 功率 P 报告结果行装配：同一 sort 下可能同时存在总有功、总无功、总视在等结果。
 */
public final class PowerResultAssembler {

    private PowerResultAssembler() {
    }

    public static List<Map<String, String>> assembleRows(List<SimAndDigNonHarmonicResult> results) {
        return assembleRows(results, Collections.emptyList());
    }

    public static List<Map<String, String>> assembleRows(List<SimAndDigNonHarmonicResult> results,
                                                         List<? extends PqScriptDtls> scriptDetails) {
        if (CollUtil.isEmpty(results)) {
            return new ArrayList<>();
        }
        Map<Integer, List<PqScriptDtls>> scriptDetailsBySort = groupScriptDetailsBySort(scriptDetails);
        Map<Integer, List<SimAndDigNonHarmonicResult>> bySort = results.stream()
                .filter(result -> result.getSort() != null)
                .collect(Collectors.groupingBy(SimAndDigNonHarmonicResult::getSort, TreeMap::new, Collectors.toList()));
        List<Map<String, String>> rows = new ArrayList<>();
        for (Map.Entry<Integer, List<SimAndDigNonHarmonicResult>> sortEntry : bySort.entrySet()) {
            List<SimAndDigNonHarmonicResult> sameSortResults = sortEntry.getValue();
            Map<String, String> row = defaultPowerRow();
            DetectionData primary = null;
            boolean hasPowerValue = false;
            for (SimAndDigNonHarmonicResult result : sameSortResults) {
                String code = result.getAdType();
                if (equalsCode(code, "TotW")) {
                    DetectionData data = parse(result.getTValue());
                    hasPowerValue |= putData(row, ItemReportKeyEnum.ACTIVE_POWER.getKey(), data);
                    primary = firstPresent(data, primary);
                } else if (equalsCode(code, "TotVAr")) {
                    hasPowerValue |= putData(row, ItemReportKeyEnum.REACTIVE_POWER.getKey(), parse(result.getTValue()));
                } else if (equalsCode(code, "TotVA")) {
                    hasPowerValue |= putData(row, ItemReportKeyEnum.APPARENT_POWER.getKey(), parse(result.getTValue()));
                } else if (equalsCode(code, "TotPF")) {
                    DetectionData data = parse(result.getTValue());
                    hasPowerValue |= putData(row, ItemReportKeyEnum.POWER_FACTOR.getKey(), data);
                } else if (equalsCode(code, "PF")) {
                    if (isBlankPowerValue(row, ItemReportKeyEnum.POWER_FACTOR.getKey())) {
                        DetectionData data = firstPresent(parse(result.getTValue()), phaseAverage(result));
                        hasPowerValue |= putData(row, ItemReportKeyEnum.POWER_FACTOR.getKey(), data);
                    }
                } else if (equalsCode(code, "W")) {
                    DetectionData[] phases = phases(result);
                    if (isBlankPowerValue(row, ItemReportKeyEnum.ACTIVE_POWER.getKey())) {
                        hasPowerValue |= putSum(row, ItemReportKeyEnum.ACTIVE_POWER.getKey(), phases);
                    }
                    putPhaseDetails(row, phases);
                    primary = preferPrimary(primary, firstPresent(phases));
                } else if (equalsCode(code, "VARW")) {
                    if (isBlankPowerValue(row, ItemReportKeyEnum.REACTIVE_POWER.getKey())) {
                        hasPowerValue |= putSum(row, ItemReportKeyEnum.REACTIVE_POWER.getKey(), phases(result));
                    }
                } else if (equalsCode(code, "VAW")) {
                    if (isBlankPowerValue(row, ItemReportKeyEnum.APPARENT_POWER.getKey())) {
                        hasPowerValue |= putSum(row, ItemReportKeyEnum.APPARENT_POWER.getKey(), phases(result));
                    }
                }
            }
            if (primary != null) {
                putPrimary(row, primary);
            }
            if (hasPowerValue) {
                putStandardSourceValues(row, sortEntry.getKey(), scriptDetailsBySort);
                rows.add(row);
            }
        }
        return rows;
    }

    private static Map<String, String> defaultPowerRow() {
        Map<String, String> row = new LinkedHashMap<>();
        List<ItemReportKeyEnum> defaultKeys = Arrays.asList(
                ItemReportKeyEnum.SCRIPT_DETAIL,
                ItemReportKeyEnum.STANDARD,
                ItemReportKeyEnum.STANDARD_A,
                ItemReportKeyEnum.STANDARD_B,
                ItemReportKeyEnum.STANDARD_C,
                ItemReportKeyEnum.STANDARD_V,
                ItemReportKeyEnum.STANDARD_I,
                ItemReportKeyEnum.STANDARD_U_ANGLE,
                ItemReportKeyEnum.STANDARD_I_ANGLE,
                ItemReportKeyEnum.TEST,
                ItemReportKeyEnum.TEST_A,
                ItemReportKeyEnum.TEST_B,
                ItemReportKeyEnum.TEST_C,
                ItemReportKeyEnum.ERROR,
                ItemReportKeyEnum.ERROR_A,
                ItemReportKeyEnum.ERROR_B,
                ItemReportKeyEnum.ERROR_C,
                ItemReportKeyEnum.ERROR_SCOPE,
                ItemReportKeyEnum.RESULT,
                ItemReportKeyEnum.RESULT_A,
                ItemReportKeyEnum.RESULT_B,
                ItemReportKeyEnum.RESULT_C,
                ItemReportKeyEnum.ACTIVE_POWER,
                ItemReportKeyEnum.REACTIVE_POWER,
                ItemReportKeyEnum.APPARENT_POWER,
                ItemReportKeyEnum.POWER_FACTOR
        );
        for (ItemReportKeyEnum key : defaultKeys) {
            row.put(key.getKey(), "/");
        }
        return row;
    }

    private static Map<Integer, List<PqScriptDtls>> groupScriptDetailsBySort(List<? extends PqScriptDtls> scriptDetails) {
        Map<Integer, List<PqScriptDtls>> bySort = new TreeMap<>();
        if (CollUtil.isEmpty(scriptDetails)) {
            return bySort;
        }
        for (PqScriptDtls detail : scriptDetails) {
            if (detail == null || detail.getScriptIndex() == null) {
                continue;
            }
            bySort.computeIfAbsent(detail.getScriptIndex(), key -> new ArrayList<>()).add(detail);
        }
        return bySort;
    }

    private static void putStandardSourceValues(Map<String, String> row, Integer sort,
                                                Map<Integer, List<PqScriptDtls>> scriptDetailsBySort) {
        List<PqScriptDtls> sameSortDetails = scriptDetailsBySort.get(sort);
        if (CollUtil.isEmpty(sameSortDetails)) {
            return;
        }
        PqScriptDtls voltage = pickPhaseDetail(sameSortDetails, "VOL");
        PqScriptDtls current = pickPhaseDetail(sameSortDetails, "CUR");
        putDouble(row, ItemReportKeyEnum.STANDARD_V.getKey(), voltage == null ? null : voltage.getValue());
        putDouble(row, ItemReportKeyEnum.STANDARD_I.getKey(), current == null ? null : current.getValue());
        putDouble(row, ItemReportKeyEnum.STANDARD_U_ANGLE.getKey(), voltage == null ? null : voltage.getAngle());
        putDouble(row, ItemReportKeyEnum.STANDARD_I_ANGLE.getKey(), current == null ? null : current.getAngle());
    }

    private static PqScriptDtls pickPhaseDetail(List<PqScriptDtls> details, String valueType) {
        for (String phase : Arrays.asList("A", "B", "C")) {
            PqScriptDtls detail = firstMatchingDetail(details, valueType, phase);
            if (detail != null) {
                return detail;
            }
        }
        return firstMatchingDetail(details, valueType, null);
    }

    private static PqScriptDtls firstMatchingDetail(List<PqScriptDtls> details, String valueType, String phase) {
        for (PqScriptDtls detail : details) {
            if (!equalsCode(detail.getValueType(), valueType)) {
                continue;
            }
            if (phase != null && !equalsCode(detail.getPhase(), phase)) {
                continue;
            }
            if (detail.getValue() != null || detail.getAngle() != null) {
                return detail;
            }
        }
        return null;
    }

    private static void putDouble(Map<String, String> row, String key, Double value) {
        if (value != null) {
            row.put(key, format(value));
        }
    }

    private static boolean putData(Map<String, String> row, String key, DetectionData data) {
        if (data == null || data.getData() == null) {
            return false;
        }
        row.put(key, format(data.getData()));
        return true;
    }

    private static boolean putSum(Map<String, String> row, String key, DetectionData[] phases) {
        BigDecimal sum = BigDecimal.ZERO;
        boolean hasValue = false;
        for (DetectionData phase : phases) {
            if (phase != null && phase.getData() != null) {
                sum = sum.add(BigDecimal.valueOf(phase.getData()));
                hasValue = true;
            }
        }
        if (hasValue) {
            row.put(key, format(sum));
        }
        return hasValue;
    }

    private static boolean isBlankPowerValue(Map<String, String> row, String key) {
        String value = row.get(key);
        return StrUtil.isBlank(value) || "/".equals(value);
    }

    private static void putPrimary(Map<String, String> row, DetectionData primary) {
        row.put(ItemReportKeyEnum.STANDARD.getKey(), valueOrSlash(primary.getResultData()));
        row.put(ItemReportKeyEnum.TEST.getKey(), valueOrSlash(primary.getData()));
        row.put(ItemReportKeyEnum.ERROR.getKey(), primary.getErrorData() == null ? "/" : format(primary.getErrorData()));
        row.put(ItemReportKeyEnum.RESULT.getKey(), resultText(primary.getIsData()));
        String errorScope = dealErrorScope(primary.getRadius());
        if (StrUtil.isNotBlank(primary.getUnit()) && !"/".equals(errorScope)) {
            errorScope = errorScope.concat(primary.getUnit());
        }
        row.put(ItemReportKeyEnum.ERROR_SCOPE.getKey(), errorScope);
    }

    private static void putPhaseDetails(Map<String, String> row, DetectionData[] phases) {
        List<String> standards = Arrays.asList(ItemReportKeyEnum.STANDARD_A.getKey(),
                ItemReportKeyEnum.STANDARD_B.getKey(), ItemReportKeyEnum.STANDARD_C.getKey());
        List<String> tests = Arrays.asList(ItemReportKeyEnum.TEST_A.getKey(),
                ItemReportKeyEnum.TEST_B.getKey(), ItemReportKeyEnum.TEST_C.getKey());
        List<String> errors = Arrays.asList(ItemReportKeyEnum.ERROR_A.getKey(),
                ItemReportKeyEnum.ERROR_B.getKey(), ItemReportKeyEnum.ERROR_C.getKey());
        List<String> results = Arrays.asList(ItemReportKeyEnum.RESULT_A.getKey(),
                ItemReportKeyEnum.RESULT_B.getKey(), ItemReportKeyEnum.RESULT_C.getKey());
        for (int i = 0; i < phases.length; i++) {
            DetectionData phase = phases[i];
            row.put(standards.get(i), phase == null ? "/" : valueOrSlash(phase.getResultData()));
            row.put(tests.get(i), phase == null ? "/" : valueOrSlash(phase.getData()));
            row.put(errors.get(i), phase == null || phase.getErrorData() == null ? "/" : format(phase.getErrorData()));
            row.put(results.get(i), phase == null ? "/" : resultText(phase.getIsData()));
        }
    }

    private static DetectionData phaseAverage(SimAndDigNonHarmonicResult result) {
        DetectionData[] phases = phases(result);
        BigDecimal sum = BigDecimal.ZERO;
        int count = 0;
        for (DetectionData phase : phases) {
            if (phase != null && phase.getData() != null) {
                sum = sum.add(BigDecimal.valueOf(phase.getData()));
                count++;
            }
        }
        if (count == 0) {
            return null;
        }
        DetectionData data = new DetectionData();
        data.setData(sum.divide(BigDecimal.valueOf(count), 8, RoundingMode.HALF_UP).doubleValue());
        return data;
    }

    private static DetectionData[] phases(SimAndDigNonHarmonicResult result) {
        return new DetectionData[]{
                parse(result.getAValue()),
                parse(result.getBValue()),
                parse(result.getCValue())
        };
    }

    private static DetectionData firstPresent(DetectionData... values) {
        if (values == null) {
            return null;
        }
        for (DetectionData value : values) {
            if (value != null) {
                return value;
            }
        }
        return null;
    }

    private static DetectionData preferPrimary(DetectionData current, DetectionData candidate) {
        return current != null ? current : candidate;
    }

    private static DetectionData parse(String value) {
        if (StrUtil.isBlank(value)) {
            return null;
        }
        return JSONUtil.toBean(value, DetectionData.class);
    }

    private static boolean equalsCode(String actual, String expected) {
        return expected.equalsIgnoreCase(actual);
    }

    private static String resultText(Integer isData) {
        if (isData == null) {
            return "/";
        }
        if (isData == 1) {
            return "合格";
        }
        if (isData == 2) {
            return "不合格";
        }
        return "/";
    }

    private static String valueOrSlash(Double value) {
        return value == null ? "/" : format(value);
    }

    private static String format(Double value) {
        return value == null ? "/" : format(BigDecimal.valueOf(value));
    }

    private static String format(BigDecimal value) {
        return value.setScale(4, RoundingMode.HALF_UP).toPlainString();
    }

    private static String dealErrorScope(String errorScope) {
        if (StrUtil.isBlank(errorScope)) {
            return "/";
        }
        if (errorScope.contains("~")) {
            String[] split = errorScope.split("~");
            String begin = split[0];
            if (begin.startsWith("-")) {
                begin = begin.substring(1);
            }
            if (split.length > 1 && split[1].equalsIgnoreCase(begin)) {
                return "±" + begin;
            }
        }
        return errorScope;
    }
}
