package com.zksy.api.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 预警等级计算工具（数据接近阈值但尚未越限时使用）
 * <p>
 * 预警区：距上下限剩余空间进入正常区间宽度（或单侧阈值绝对值）的 30% 时进入预警。
 * 级别 1 最高，4 最低，按逼近程度定级。
 */
public class EarlyWarningLevelUtil {

    /** 预警带宽占正常区间（或单侧阈值绝对值）的比例 */
    private static final BigDecimal BAND_RATIO = new BigDecimal("0.30");

    private static final BigDecimal FORTY = new BigDecimal("40");
    private static final BigDecimal SIXTY_FIVE = new BigDecimal("65");
    private static final BigDecimal EIGHTY_FIVE = new BigDecimal("85");
    private static final BigDecimal HUNDRED = new BigDecimal("100");

    private EarlyWarningLevelUtil() {
    }

    /**
     * 计算预警级别结果；未进入预警区或已越限时返回 null（应由报警逻辑处理）
     *
     * @param actual 实际值
     * @param min    下限（可空）
     * @param max    上限（可空）
     * @return 预警级别结果，无需预警时为 null
     */
    public static EarlyWarningLevelResult calculate(BigDecimal actual, BigDecimal min, BigDecimal max) {
        if (actual == null || (min == null && max == null)) {
            return null;
        }

        // 已越限：不生成预警
        if (isOverThreshold(actual, min, max)) {
            return null;
        }

        BigDecimal band = calcBand(min, max);
        if (band == null || band.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }

        EarlyWarningLevelResult upper = evaluateUpper(actual, max, band);
        EarlyWarningLevelResult lower = evaluateLower(actual, min, band);

        if (upper == null) {
            return lower;
        }
        if (lower == null) {
            return upper;
        }
        // 双侧同时接近时，取更紧急的级别（数值更小）
        return upper.getLevel() <= lower.getLevel() ? upper : lower;
    }

    /**
     * 是否已达到或超过报警阈值
     */
    public static boolean isOverThreshold(BigDecimal actual, BigDecimal min, BigDecimal max) {
        if (actual == null) {
            return false;
        }
        if (min != null && actual.compareTo(min) <= 0) {
            return true;
        }
        return max != null && actual.compareTo(max) >= 0;
    }

    /**
     * 是否处于预警区（接近但未越限）
     */
    public static boolean isInWarningZone(BigDecimal actual, BigDecimal min, BigDecimal max) {
        return calculate(actual, min, max) != null;
    }

    private static EarlyWarningLevelResult evaluateUpper(BigDecimal actual, BigDecimal max, BigDecimal band) {
        if (max == null) {
            return null;
        }
        BigDecimal warnStart = max.subtract(band);
        // 进入上限预警区：warnStart <= actual < max
        if (actual.compareTo(warnStart) < 0 || actual.compareTo(max) >= 0) {
            return null;
        }
        BigDecimal approachRatio = actual.subtract(warnStart)
                .divide(band, 4, RoundingMode.HALF_UP)
                .multiply(HUNDRED);
        return new EarlyWarningLevelResult(mapToLevel(approachRatio), approachRatio, "UPPER");
    }

    private static EarlyWarningLevelResult evaluateLower(BigDecimal actual, BigDecimal min, BigDecimal band) {
        if (min == null) {
            return null;
        }
        BigDecimal warnEnd = min.add(band);
        // 进入下限预警区：min < actual <= warnEnd
        if (actual.compareTo(min) <= 0 || actual.compareTo(warnEnd) > 0) {
            return null;
        }
        // 逼近率：刚进入 warnEnd 为 0%，贴近 min 为 100%
        BigDecimal approachRatio = warnEnd.subtract(actual)
                .divide(band, 4, RoundingMode.HALF_UP)
                .multiply(HUNDRED);
        if (approachRatio.compareTo(BigDecimal.ZERO) < 0) {
            approachRatio = BigDecimal.ZERO;
        }
        if (approachRatio.compareTo(HUNDRED) > 0) {
            approachRatio = HUNDRED;
        }
        return new EarlyWarningLevelResult(mapToLevel(approachRatio), approachRatio, "LOWER");
    }

    private static BigDecimal calcBand(BigDecimal min, BigDecimal max) {
        if (min != null && max != null) {
            BigDecimal range = max.subtract(min).abs();
            if (range.compareTo(BigDecimal.ZERO) == 0) {
                BigDecimal base = max.abs().compareTo(BigDecimal.ZERO) > 0 ? max.abs() : BigDecimal.ONE;
                return base.multiply(BAND_RATIO);
            }
            return range.multiply(BAND_RATIO);
        }
        if (max != null) {
            BigDecimal abs = max.abs();
            return abs.compareTo(BigDecimal.ZERO) > 0 ? abs.multiply(BAND_RATIO) : BigDecimal.ONE;
        }
        if (min != null) {
            BigDecimal abs = min.abs();
            return abs.compareTo(BigDecimal.ZERO) > 0 ? abs.multiply(BAND_RATIO) : BigDecimal.ONE;
        }
        return null;
    }

    /**
     * 逼近率 → 预警级别
     * <ul>
     *   <li>[0, 40) → 4 提醒（刚进入预警区）</li>
     *   <li>[40, 65) → 3 警告</li>
     *   <li>[65, 85) → 2 高危</li>
     *   <li>[85, 100] → 1 紧急（即将越限）</li>
     * </ul>
     */
    public static int mapToLevel(BigDecimal approachRatio) {
        if (approachRatio == null) {
            return 4;
        }
        if (approachRatio.compareTo(FORTY) < 0) {
            return 4;
        }
        if (approachRatio.compareTo(SIXTY_FIVE) < 0) {
            return 3;
        }
        if (approachRatio.compareTo(EIGHTY_FIVE) < 0) {
            return 2;
        }
        return 1;
    }

    public static String getLevelName(int level) {
        switch (level) {
            case 1:
                return "1级（紧急）";
            case 2:
                return "2级（高危）";
            case 3:
                return "3级（警告）";
            case 4:
                return "4级（提醒）";
            default:
                return "未知等级";
        }
    }

    /**
     * 预警级别计算结果
     */
    public static class EarlyWarningLevelResult {
        private final int level;
        private final BigDecimal approachRatio;
        private final String side;

        public EarlyWarningLevelResult(int level, BigDecimal approachRatio, String side) {
            this.level = level;
            this.approachRatio = approachRatio;
            this.side = side;
        }

        public int getLevel() {
            return level;
        }

        public BigDecimal getApproachRatio() {
            return approachRatio;
        }

        public String getSide() {
            return side;
        }
    }
}
