package com.awspaas.user.apps.donenow_ctt;

import com.actionsoft.sdk.local.SDK;
import com.actionsoft.sdk.local.api.Logger;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 周期计算工具类
 * 支持：月/季/半年/年周期、自然月/实际月模式、周期列表生成与费率计算
 */
public class PeriodCalculationUtil {
    private static final Logger LOGGERU = SDK.getLogAPI().getLogger(PeriodCalculationUtil.class);//记录日志
    // 周期类型常量（与原IVTConstant对齐，可根据实际常量类调整）
    public static final String PERIOD_TYPE_ONCE = "609";       // 一次性收费
    public static final String PERIOD_TYPE_MONTH = "610";     // 按月支付
    public static final String PERIOD_TYPE_QUARTER = "611"; // 按季支付
    public static final String PERIOD_TYPE_HALFYEAR = "612"; // 按半年支付
    public static final String PERIOD_TYPE_YEAR = "613";       // 按年支付

    public static final String PERIOD_TYPE_TWOYEAR = "614";       // 按2年支付

    // 日期格式化器（复用避免重复创建）
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");

    /**
     * 私有构造：防止工具类实例化
     */
    private PeriodCalculationUtil() {
        throw new AssertionError("工具类不允许实例化");
    }


    /**
     * CALC_METHOD_1STPERIOD/PURCHASE_CALC_METHOD_1STPERIOD
     * 610	按月支付	4601	自然月 isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
     * 610	按月支付	4602	开通日期后的完整1个月  isNaturalMonth=false userMonthDay=true   monthDayType=0 startMonth=0
     *
     * 611	按季度支付	4596	开通日期所在月+后续2个自然月 isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
     *
     * 611	按季度支付	4597	开通日期所在月 isNaturalMonth=fasle userMonthDay=true  monthDayType=-2 startMonth
     *
     * 611	按季度支付	4598	开通日期后的完整一个季度 isNaturalMonth=true userMonthDay=true  monthDayType=0 startMonth=0
     * 611	按季度支付	4599	首季度根据固定季度计算 isNaturalMonth=false userMonthDay=true  monthDayType=-3  startMonth=0
     *
     *
     * 612	按半年支付	4593	开通日期所在月+后续5个自然月   isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
     * 612	按半年支付	4594	开通日期所在月  isNaturalMonth=true userMonthDay=true  monthDayType=-2 startMonth=0
     * 612	按半年支付	4595	开通日期后的完整半年 isNaturalMonth=false userMonthDay=true monthDayType=0 startMonth=0
     *
     * 613	按年支付	4590	开通日期所在月+后续11个自然月  isNaturalMonth=false  CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true monthDayType=-1 startMonth=0
     * 613	按年支付	4591	开通日期所在月  isNaturalMonth=false CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true  monthDayType=-2 startMonth=0
     * 613	按年支付	4592	开通日期后的完整一年 isNaturalMonth=false CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true monthDayType=0 startMonth=0
     *
     *
     * 614 按两年付  4603/4604/4605
     *
     *CALC_METHOD_1STYEAR
     * 4637	（拆机时实际计费天数/当年天数（如365））*年费用
     * 4638	先算月再算天
     *
     *
     * MONTH_1STQUARTER
     * 4642	1月 startMonth=1
     * 4643	11月 startMonth=11
     * 4644	12月 startMonth=12
     */

    /**
     * 获取周期列表通过配置
     * @param ruleCate 周期类型
     * @param contractStartDate 计费开始日期
     * @param periodBeginDate 周期开始日期
     * @param periodEndDate 周期结束日期
     * @param CALC_METHOD_1STPERIOD
     * @param CALC_METHOD_1STYEAR
     * @param MONTH_1STQUARTER
     * @return
     */
    public static List<Period> getPeriodListByConfig(String ruleCate, LocalDate contractStartDate, LocalDate periodBeginDate, LocalDate periodEndDate, String CALC_METHOD_1STPERIOD, String CALC_METHOD_1STYEAR, String MONTH_1STQUARTER) {
        // 初始化默认值
        boolean isNaturalMonth = true;
        boolean userMonthDay = true;
        int monthDayType = 0;
        int startMonth = 0;

        //611	按季度支付	4599	首季度根据固定季度计算
        if (CALC_METHOD_1STPERIOD.equals("4599")) {
            switch (MONTH_1STQUARTER) {
                case "4642":
                    startMonth = 1;
                    break;
                case "4643":
                    startMonth = 11;
                    break;
                case "4644":
                    startMonth = 12;
                    break;
                default:
                    startMonth = 0;
                    break;
            }
        }

        switch (CALC_METHOD_1STPERIOD) {
            // 合并：isNaturalMonth=true, userMonthDay=true, monthDayType=-1 的情况
            case "4601":
            case "4596":
            case "4593":
            case "4590":
            case "4603":
                monthDayType = -1;
                break;

            // 合并：isNaturalMonth=true, userMonthDay=true, monthDayType=-2 的情况
            case "4597":
            case "4594":
            case "4591":
            case "4604":
                monthDayType = -2;
                break;
        }

        //按年支付
        if (ruleCate.equals(PERIOD_TYPE_YEAR)) {
            isNaturalMonth = false;//不需要切换到 1月1日
            userMonthDay = !"4637".equals(CALC_METHOD_1STYEAR);
        }

        //按半年支付
        if (ruleCate.equals(PERIOD_TYPE_HALFYEAR)) {
            isNaturalMonth = false;//不需要切换到 1月1日 或者 7月1日
        }
        if (CALC_METHOD_1STPERIOD.equals("4601"))
            isNaturalMonth = true;
        else
            isNaturalMonth = false;

        return getPeriodList(ruleCate, contractStartDate, periodBeginDate, periodEndDate,
                isNaturalMonth, userMonthDay, monthDayType, startMonth);
    }


    /**
     * 核心方法：生成周期列表（含费率）
     *
     * @param ruleCate          周期类型（参考类内PERIOD_TYPE_*常量）
     * @param contractStartDate 合同开始日期
     * @param periodBeginDate   服务开始日期（周期计算起始点）
     * @param periodEndDate     服务结束日期（周期计算终点）
     * @param isNaturalMonth    是否自然月模式（true=自然月，false=实际月）
     * @param userMonthDay       是否使用当月实际天数用于计算每天费用
     * @param monthDayType  monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天，大于这个天数则使用这个天数
     * @param startMonth  指定开始月份
     * @return 周期列表（含起止日期、费率）
     * @throws IllegalArgumentException 当参数非法时抛出（如日期null、周期类型无效）
     */
    public static List<Period> getPeriodList(
            String ruleCate,
            LocalDate contractStartDate,
            LocalDate periodBeginDate,
            LocalDate periodEndDate,
            boolean isNaturalMonth,
            boolean userMonthDay,
            int monthDayType,
            int startMonth
    ) {
        LOGGERU.info("【周期计算】开始生成周期列表");

        LOGGERU.info("【周期计算】参数：ruleCate=" + ruleCate + ", contractStartDate=" + contractStartDate + ", periodBeginDate=" + periodBeginDate + ", periodEndDate=" + periodEndDate + ", isNaturalMonth=" + isNaturalMonth + ", userMonthDay=" + userMonthDay + ", monthDayType=" + monthDayType + ",startMonth=" + startMonth);

        LOGGERU.info("周期开始日期--计算前：" + contractStartDate);

        // 1. 参数校验：避免空指针和非法参数
        if (validateParams(ruleCate, contractStartDate, periodBeginDate, periodEndDate) == false) {
            return null;
        }

        List<Period> periodList = new ArrayList<>();
        // 2. 一次性收费：直接生成单个周期
        if (PERIOD_TYPE_ONCE.equals(ruleCate)) {
            Period oncePeriod = new Period();
            oncePeriod.setPeriodBeginDate(periodBeginDate);
            oncePeriod.setPeriodEndDate(periodEndDate);
            oncePeriod.setRate(BigDecimal.ONE);
            periodList.add(oncePeriod);
            return periodList;
        }

        //实际月
        if (isNaturalMonth == false && (contractStartDate.getDayOfMonth() != 1)) {
            // monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天，大于这个天数则使用这个天数
            //本月1号
            if (monthDayType == -1)
                contractStartDate = contractStartDate.with(TemporalAdjusters.firstDayOfMonth());
                //下月1号
            else if (monthDayType == -2)
                contractStartDate = contractStartDate.with(TemporalAdjusters.firstDayOfNextMonth());
                //指定日期
            else if (monthDayType > 0) {
                int totalDays = contractStartDate.lengthOfMonth(); // 获取当月总天数
                contractStartDate = monthDayType > totalDays ?
                        LocalDate.of(contractStartDate.getYear(), contractStartDate.getMonth(), totalDays) :  // 超过总天数→最后一天
                        LocalDate.of(contractStartDate.getYear(), contractStartDate.getMonth(), monthDayType);// 否则→当月第day天
            }
            //不修改
        }

        //指定开始月份  11、12、1
        if (startMonth > 0) {
            contractStartDate = getClosestPeriodStartDate(contractStartDate, startMonth);
        }

        LOGGERU.info("周期开始日期--计算后：" + contractStartDate);

        // 3. 非一次性收费：按周期类型计算（月/季/半年/年）
        LocalDate startDate = null;
        LocalDate currLastDay = null; // 当前周期结束日
        int totalPeriodDays = 0;      // 完整周期总天数（用于计算费率）

        // 3.1 计算首个周期的结束日和完整周期天数
        switch (ruleCate) {
            case PERIOD_TYPE_MONTH:
                if (isNaturalMonth) {
                    currLastDay = getNaturalMonthLastDay(periodBeginDate);
                    totalPeriodDays = periodBeginDate.lengthOfMonth();
                } else {
                    currLastDay = getActualMonthFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
                    totalPeriodDays = getActualMonthTotalDays(contractStartDate, periodBeginDate);
                }
                break;

            case PERIOD_TYPE_QUARTER:
                if (isNaturalMonth) {
                    currLastDay = getNaturalQuarterLastDay(periodBeginDate);
                    totalPeriodDays = getDaysInCurrentQuarter(periodBeginDate);
                } else {
                    currLastDay = getActualQuarterFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);

                    totalPeriodDays = getActualQuarterTotalDays(contractStartDate, periodBeginDate);
                }
                break;

            case PERIOD_TYPE_HALFYEAR:
                if (isNaturalMonth) {
                    currLastDay = getNaturalHalfYearLastDay(periodBeginDate);
                    totalPeriodDays = getDaysInCurrentHalfYear(periodBeginDate);
                } else {
                    currLastDay = getActualHalfYearFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
                    totalPeriodDays = getActualHalfYearTotalDays(contractStartDate, periodBeginDate);
                }
                break;

            case PERIOD_TYPE_YEAR:
                if (isNaturalMonth) {
                    currLastDay = getNaturalYearLastDay(periodBeginDate);
                    totalPeriodDays = periodBeginDate.lengthOfYear();
                } else {
                    currLastDay = getActualYearFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
                    totalPeriodDays = getActualYearTotalDays(contractStartDate, periodBeginDate);
                }
                break;

            case PERIOD_TYPE_TWOYEAR://2年
                if (isNaturalMonth) {
                    currLastDay = getNaturalYearLastDay(periodBeginDate);
                    totalPeriodDays = periodBeginDate.lengthOfYear();
                } else {
                    currLastDay = getActualTWOYearFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
                    totalPeriodDays = getActualYearTotalDays(contractStartDate, periodBeginDate);
                }
                break;
            default:
                throw new IllegalArgumentException("不支持的周期类型：" + ruleCate);
        }

        // 3.2 处理首个周期
        if (currLastDay != null && currLastDay.equals(periodBeginDate) == false) {

            if (userMonthDay) {
                Period period = new Period();
                period.setPeriodBeginDate(periodBeginDate);
                period.setPeriodEndDate(currLastDay);

                //月占天数比例  整月个数
                period.setRate(divideToBigDecimal(getDateRate(periodBeginDate, currLastDay, false), GetPeriodMonth(ruleCate)));

                periodList.add(period);
            } else {
                Period firstPeriod = buildPeriod(periodBeginDate, currLastDay, totalPeriodDays);
                periodList.add(firstPeriod);
            }

            // 更新下一个周期的起始日（当前周期结束日+1）
            startDate = currLastDay.plusDays(1);
        } else {
            startDate = periodBeginDate;
        }

        // 3.3 循环生成后续周期（直到超过服务结束日）
        if (startDate != null && !startDate.isAfter(periodEndDate)) {
            while (!startDate.isAfter(periodEndDate)) {
                LocalDate nextPeriodStart = getNextPeriodStart(startDate, ruleCate);
                LocalDate periodEnd = nextPeriodStart.minusDays(1); // 当前周期结束日

                // 若周期结束日超过服务结束日，截断并计算实际费率
                BigDecimal rate = BigDecimal.ONE;
                if (periodEnd.isAfter(periodEndDate)) {
                    if (userMonthDay) {
                        rate = divideToBigDecimal(getDateRate(startDate, periodEndDate, false), GetPeriodMonth(ruleCate));
                    } else {
                        int actualDays = getPeriodDays(startDate, periodEndDate);
                        int fullPeriodDays = getPeriodDays(startDate, periodEnd);
                        rate = divideToBigDecimal(actualDays, fullPeriodDays);
                    }
                    periodEnd = periodEndDate;
                }

                Period cyclePeriod = new Period();
                cyclePeriod.setPeriodBeginDate(startDate);
                cyclePeriod.setPeriodEndDate(periodEnd);
                cyclePeriod.setRate(rate);
                periodList.add(cyclePeriod);

                // 更新下一个周期起始日
                startDate = periodEnd.plusDays(1);
            }
        }

        LOGGERU.info("周期列表个数：" + periodList.size());
        LOGGERU.info("周期列表：" + JSON.toJSONString(periodList));
        return periodList;
    }

    /**
     * 计算最接近的周期开始日期（支持1-12月所有起始月份）
     * 每个起始月份对应4个周期，每个周期包含3个连续月份
     * @param contractStartDate 合同开始日期
     * @param startMonth 指定的起始月份（1-12）
     * @return 对应的周期开始日期（每月1日）
     */
    public static LocalDate getClosestPeriodStartDate(LocalDate contractStartDate, int startMonth) {
        Objects.requireNonNull(contractStartDate, "合同开始日期不能为空");
        if (startMonth < 1 || startMonth > 12) {
            throw new IllegalArgumentException("起始月份必须在1-12之间");
        }

        // 计算合同日期的总月数（从1970年1月1日开始的月数）
        long totalMonths = (contractStartDate.getYear() - 1970) * 12L + (contractStartDate.getMonthValue() - 1);
        // 起始月份的基准月（0-based）
        long baseMonth = startMonth - 1;

        long periodIndex = (totalMonths - baseMonth) / 3;
        if ((totalMonths - baseMonth) < 0 && (totalMonths - baseMonth) % 3 != 0) {
            periodIndex--;
        }

        // 计算周期起始的总月数
        long periodStartTotal = baseMonth + 3 * periodIndex;

        // 转换为年月
        int year = 1970 + (int) (periodStartTotal / 12);
        int month = (int) (periodStartTotal % 12) + 1;

        return LocalDate.of(year, month, 1);
    }

    /**
     * 获取修改结束日期调整后的周期列表
     * @param ruleCate 周期类型
     * @param lastPeriodBeginDate 最后一个周期开始日期
     * @param lastPeriodEndDate  最后一个周期的结束日期
     * @param userMonthDay  是否使用当月实际天数用于计算每天费用
     * @param newEndDate   新的结束日期  lastPeriodBeginDate<=  newEndDate
     * @return
     */
    public static List<Period> getAdjustPeriodList(
            String ruleCate,
            LocalDate lastPeriodBeginDate,
            LocalDate lastPeriodEndDate,
            boolean userMonthDay,
            LocalDate newEndDate
    ) {

        List<Period> periodList = new ArrayList<>();

        if (lastPeriodEndDate.equals(newEndDate)) {
            return null;//不需要生成新的周期，把原来周期中 lastPeriodEndDate 之后的删除就行
        }


        if (lastPeriodEndDate.isAfter(newEndDate)) {
            //周期缩短

            if (userMonthDay) {
                Period period = new Period();
                period.setPeriodBeginDate(lastPeriodBeginDate);
                period.setPeriodEndDate(newEndDate);
                period.setRate(divideToBigDecimal(getDateRate(lastPeriodBeginDate, newEndDate, false), GetPeriodMonth(ruleCate)));
                periodList.add(period);

            } else {
                periodList.add(buildPeriod(lastPeriodBeginDate, lastPeriodEndDate, getPeriodDays(lastPeriodBeginDate, lastPeriodEndDate)));

            }

        } else {
            //周期延长

            //1、如果 lastPeriodBeginDate 到 lastPeriodEndDate  不是一个完整周期，要先修改成完整周期

            LocalDate currPeriodEnd = getNextPeriodStart(lastPeriodBeginDate, ruleCate).minusDays(1);// 当前周期结束日

            if (currPeriodEnd.isAfter(lastPeriodEndDate)) {
                lastPeriodEndDate = newEndDate;
                if (userMonthDay) {
                    Period period = new Period();
                    period.setPeriodBeginDate(lastPeriodBeginDate);
                    period.setPeriodEndDate(currPeriodEnd);
                    //月占天数比例  整月个数
                    period.setRate(divideToBigDecimal(getDateRate(lastPeriodBeginDate, currPeriodEnd, false), GetPeriodMonth(ruleCate)));
                    periodList.add(period);
                } else {
                    periodList.add(buildPeriod(lastPeriodBeginDate, lastPeriodEndDate, getPeriodDays(lastPeriodBeginDate, currPeriodEnd)));
                }
                return periodList;
            } else {
                LocalDate startDate = lastPeriodBeginDate;

                if (startDate != null && !startDate.isAfter(newEndDate)) {
                    while (!startDate.isAfter(newEndDate)) {

                        LocalDate nextPeriodStart = getNextPeriodStart(startDate, ruleCate);
                        LocalDate periodEnd = nextPeriodStart.minusDays(1); // 当前周期结束日

                        // 若周期结束日超过服务结束日，截断并计算实际费率
                        BigDecimal rate = BigDecimal.ONE;
                        if (periodEnd.isAfter(newEndDate)) {
                            if (userMonthDay) {
                                rate = divideToBigDecimal(getDateRate(startDate, newEndDate, false), GetPeriodMonth(ruleCate));
                            } else {
                                int actualDays = getPeriodDays(startDate, newEndDate);
                                int fullPeriodDays = getPeriodDays(startDate, periodEnd);
                                rate = divideToBigDecimal(actualDays, fullPeriodDays);
                            }
                            periodEnd = newEndDate;
                        }

                        Period cyclePeriod = new Period();
                        cyclePeriod.setPeriodBeginDate(startDate);
                        cyclePeriod.setPeriodEndDate(periodEnd);
                        cyclePeriod.setRate(rate);
                        periodList.add(cyclePeriod);

                        // 更新下一个周期起始日
                        startDate = periodEnd.plusDays(1);
                    }
                }
            }
        }
        return periodList;
    }


    // ------------------------------ 私有工具方法：参数校验 ------------------------------

    /**
     * 参数校验：防止空指针和非法日期范围
     */
    private static boolean validateParams(
            String ruleCate,
            LocalDate contractStartDate,
            LocalDate periodBeginDate,
            LocalDate periodEndDate
    ) {
        if (StringUtils.isBlank(ruleCate)) {
            LOGGERU.info("周期类型（ruleCate）不能为空");
            return false;
        }
        if (contractStartDate == null) {
            LOGGERU.info("合同开始日期（contractStartDate）不能为空");
            return false;
        }
        if (periodBeginDate == null) {
            LOGGERU.info("服务开始日期（periodBeginDate）不能为空");
            return false;
        }
        if (periodEndDate == null) {
            LOGGERU.info("服务结束日期（periodEndDate）不能为空");
            return false;
        }
        if (periodBeginDate.isAfter(periodEndDate)) {
            LOGGERU.info("服务开始日期不能晚于服务结束日期");
            return false;
        }

        return true;
    }

    // ------------------------------ 私有工具方法：自然月模式计算 ------------------------------

    /**
     * 自然月：获取当前月的最后一天（处理非1号开始的情况）
     */
    private static LocalDate getNaturalMonthLastDay(LocalDate periodBeginDate) {
        return periodBeginDate.getDayOfMonth() != 1
                ? periodBeginDate.with(TemporalAdjusters.lastDayOfMonth())
                : periodBeginDate.plusMonths(1).minusDays(1);
    }

    /**
     * 自然季度：获取当前季度的最后一天（处理非季度初开始的情况）
     */
    private static LocalDate getNaturalQuarterLastDay(LocalDate periodBeginDate) {
        Month endMonth = getQuarterEndMonth(periodBeginDate.getMonth());
        return LocalDate.of(periodBeginDate.getYear(), endMonth, endMonth.length(periodBeginDate.isLeapYear()));
    }

    /**
     * 自然半年：获取当前半年的最后一天（处理非半年初开始的情况）
     */
    private static LocalDate getNaturalHalfYearLastDay(LocalDate periodBeginDate) {
        Month endMonth = getHalfYearEndMonth(periodBeginDate.getMonth());
        return LocalDate.of(periodBeginDate.getYear(), endMonth, endMonth.length(periodBeginDate.isLeapYear()));
    }

    /**
     * 自然年：获取当前年的最后一天（处理非年初开始的情况）
     */
    private static LocalDate getNaturalYearLastDay(LocalDate periodBeginDate) {
        return (periodBeginDate.getDayOfMonth() != 1 || periodBeginDate.getMonthValue() != 1)
                ? LocalDate.of(periodBeginDate.getYear(), 12, 31)
                : periodBeginDate.plusYears(1).minusDays(1);
    }

    // ------------------------------ 私有工具方法：实际月模式计算 ------------------------------

    /**
     * 实际月：首个周期结束日（根据合同开始日期与服务开始日期的关系）
     */
    private static LocalDate getActualMonthFirstEndDay(
            LocalDate contractStartDate,
            LocalDate periodBeginDate,
            LocalDate periodEndDate
    ) {
        LocalDate endDay;
        if (contractStartDate.isAfter(periodBeginDate)) {
            endDay = contractStartDate.minusDays(1);
        } else if (contractStartDate.isBefore(periodBeginDate)) {
            endDay = contractStartDate.plusMonths(1);
            while (endDay.isBefore(periodBeginDate)) {
                endDay = endDay.plusMonths(1);
            }
            endDay = endDay.minusDays(1);
        } else {
            // 合同开始日 = 服务开始日：首个周期为1个月后
            //endDay = contractStartDate.plusMonths(1).minusDays(1);
            return null;
        }
        // 若结束日超过服务结束日，截断
        return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
    }

    /**
     * 实际月：完整周期总天数（用于计算费率）
     */
    private static int getActualMonthTotalDays(LocalDate contractStartDate, LocalDate periodBeginDate) {
        if (contractStartDate.isAfter(periodBeginDate)) {
            return getPeriodDays(contractStartDate.minusMonths(1), contractStartDate.minusDays(1));
        } else {
            return getPeriodDays(contractStartDate, contractStartDate.plusMonths(1).minusDays(1));
        }
    }

    /**
     * 实际季度：首个周期结束日
     */
    private static LocalDate getActualQuarterFirstEndDay(
            LocalDate contractStartDate,
            LocalDate periodBeginDate,
            LocalDate periodEndDate
    ) {
        LocalDate endDay;
        if (contractStartDate.isAfter(periodBeginDate)) {
            endDay = contractStartDate.minusDays(1);
        } else if (contractStartDate.isBefore(periodBeginDate)) {
            endDay = contractStartDate.plusMonths(3);

            while (endDay.isBefore(periodBeginDate)) {
                endDay = endDay.plusMonths(3);
            }
            endDay = endDay.minusDays(1);
        } else {
            //endDay = contractStartDate.plusMonths(3).minusDays(1);
            return null;
        }
        return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
    }

    /**
     * 实际季度：完整周期总天数
     */
    private static int getActualQuarterTotalDays(LocalDate contractStartDate, LocalDate periodBeginDate) {
        if (contractStartDate.isAfter(periodBeginDate)) {
            return getPeriodDays(contractStartDate.minusMonths(3), contractStartDate.minusDays(1));
        } else {
            return getPeriodDays(contractStartDate, contractStartDate.plusMonths(3).minusDays(1));
        }
    }

    /**
     * 实际半年：首个周期结束日
     */
    private static LocalDate getActualHalfYearFirstEndDay(
            LocalDate contractStartDate,
            LocalDate periodBeginDate,
            LocalDate periodEndDate
    ) {
        LocalDate endDay;
        if (contractStartDate.isAfter(periodBeginDate)) {
            endDay = contractStartDate.minusDays(1);
        } else if (contractStartDate.isBefore(periodBeginDate)) {
            endDay = contractStartDate.plusMonths(6);

            while (endDay.isBefore(periodBeginDate)) {
                endDay = endDay.plusMonths(6);
            }

            endDay = endDay.minusDays(1);

        } else {
            //endDay = contractStartDate.plusMonths(6).minusDays(1);
            return null;
        }
        return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
    }

    /**
     * 实际半年：完整周期总天数
     */
    private static int getActualHalfYearTotalDays(LocalDate contractStartDate, LocalDate periodBeginDate) {
        if (contractStartDate.isAfter(periodBeginDate)) {
            return getPeriodDays(contractStartDate.minusMonths(6), contractStartDate.minusDays(1));
        } else {
            return getPeriodDays(contractStartDate, contractStartDate.plusMonths(6).minusDays(1));
        }
    }

    /**
     * 实际年：首个周期结束日
     */
    private static LocalDate getActualYearFirstEndDay(
            LocalDate contractStartDate,
            LocalDate periodBeginDate,
            LocalDate periodEndDate
    ) {
        LocalDate endDay;
        if (contractStartDate.isAfter(periodBeginDate)) {
            endDay = contractStartDate.minusDays(1);
        } else if (contractStartDate.isBefore(periodBeginDate)) {
            endDay = contractStartDate.plusMonths(12);
            while (endDay.isBefore(periodBeginDate)) {
                endDay = endDay.plusMonths(12);
            }
            endDay = endDay.minusDays(1);
        } else {
            // endDay = contractStartDate.plusMonths(12).minusDays(1);
            return null;
        }
        return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
    }

    private static LocalDate getActualTWOYearFirstEndDay(
            LocalDate contractStartDate,
            LocalDate periodBeginDate,
            LocalDate periodEndDate
    ) {
        LocalDate endDay;
        if (contractStartDate.isAfter(periodBeginDate)) {
            endDay = contractStartDate.minusDays(1);
        } else if (contractStartDate.isBefore(periodBeginDate)) {
            endDay = contractStartDate.plusMonths(24);
            while (endDay.isBefore(periodBeginDate)) {
                endDay = endDay.plusMonths(24);
            }
            endDay = endDay.minusDays(1);
        } else {
            return null;
        }
        return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
    }

    /**
     * 实际年：完整周期总天数
     */
    private static int getActualYearTotalDays(LocalDate contractStartDate, LocalDate periodBeginDate) {
        if (contractStartDate.isAfter(periodBeginDate)) {
            return getPeriodDays(contractStartDate.minusMonths(12), contractStartDate.minusDays(1));
        } else {
            return getPeriodDays(contractStartDate, contractStartDate.plusMonths(12).minusDays(1));
        }
    }

    // ------------------------------ 私有工具方法：通用计算 ------------------------------

    /**
     * 构建单个周期（含费率计算）
     */
    private static Period buildPeriod(LocalDate beginDate, LocalDate endDate, int totalPeriodDays) {
        Period period = new Period();
        period.setPeriodBeginDate(beginDate);
        period.setPeriodEndDate(endDate);
        // 计算费率：实际天数 / 完整周期总天数
        int actualDays = getPeriodDays(beginDate, endDate);
        period.setRate(divideToBigDecimal(actualDays, totalPeriodDays));
        return period;
    }

    /**
     * 获取下一周期的起始日（适配 Java 8，替换不兼容的 switch 表达式）
     * @param currentStart 当前周期的起始日期（如 2024-01-15）
     * @param periodType 周期类型（参考类内 PERIOD_TYPE_* 常量：月/季/半年/年）
     * @return 下一周期的起始日期（如当前是 2024-01-15 按月，返回 2024-02-15）
     * @throws IllegalArgumentException 当传入不支持的周期类型时抛出
     */
    public static LocalDate getNextPeriodStart(LocalDate currentStart, String periodType) {
        // Java 8 仅支持传统 switch 语句，需用 case + return 实现分支逻辑
        switch (periodType) {
            case PERIOD_TYPE_MONTH:
                // 月周期：当前日期加 1 个月
                return currentStart.plus(1, ChronoUnit.MONTHS);
            case PERIOD_TYPE_QUARTER:
                // 季周期：当前日期加 3 个月
                return currentStart.plus(3, ChronoUnit.MONTHS);
            case PERIOD_TYPE_HALFYEAR:
                // 半年周期：当前日期加 6 个月
                return currentStart.plus(6, ChronoUnit.MONTHS);
            case PERIOD_TYPE_YEAR:
                // 年周期：当前日期加 1 年
                return currentStart.plus(1, ChronoUnit.YEARS);
            case PERIOD_TYPE_TWOYEAR:
                // 2 年周期：当前日期加 2 年
                return currentStart.plus(2, ChronoUnit.YEARS);
            default:
                // 不支持的周期类型：抛出异常并提示具体类型
                throw new IllegalArgumentException("不支持的周期类型：" + periodType);
        }
    }

    /**
     * 计算两个日期之间的天数（包含首尾日期）
     */
    public static int getPeriodDays(LocalDate start, LocalDate end) {
        if (start.isAfter(end)) {
            throw new IllegalArgumentException("起始日期不能晚于结束日期");
        }
        return (int) ChronoUnit.DAYS.between(start, end) + 1;
    }

    /**
     * 整数除法转BigDecimal（保留10位小数，四舍五入）
     */
    public static BigDecimal divideToBigDecimal(int numerator, int denominator) {
        if (denominator == 0) {
            throw new ArithmeticException("分母不能为零");
        }
        return new BigDecimal(numerator)
                .divide(new BigDecimal(denominator), 10, RoundingMode.HALF_UP);
    }

    public static BigDecimal divideToBigDecimal(Object numerator, Object denominator) {
        // 转换分子为BigDecimal
        BigDecimal num = toBigDecimal(numerator);
        // 转换分母为BigDecimal
        BigDecimal den = toBigDecimal(denominator);

        // 检查分母是否为0
        if (den.compareTo(BigDecimal.ZERO) == 0) {
            throw new ArithmeticException("分母不能为零");
        }
        // 精确除法（保留10位小数，四舍五入）
        return num.divide(den, 10, RoundingMode.HALF_UP);
    }

    /**
     * 将Object转换为BigDecimal
     * @param obj 待转换的对象（支持Number、String等可转换为数字的类型）
     * @return 转换后的BigDecimal
     * @throws IllegalArgumentException 若对象无法转换为BigDecimal
     */
    public static BigDecimal toBigDecimal(Object obj) {
        if (obj == null) {
            //throw new IllegalArgumentException("对象不能为null");
            return BigDecimal.ZERO;
        }

        if (obj instanceof BigDecimal) {
            return (BigDecimal) obj;
        } else if (obj instanceof Number) {
            // 处理数字类型（Integer、Double、Long等）
            return new BigDecimal(obj.toString());
        } else if (obj instanceof String) {
            // 处理字符串类型（需符合数字格式）
            try {
                return new BigDecimal((String) obj);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("字符串无法转换为BigDecimal: " + obj, e);
            }
        } else {
            throw new IllegalArgumentException("不支持的类型转换: " + obj.getClass().getName());
        }
    }

    // ------------------------------ 私有工具方法：周期起止月计算 ------------------------------

    /**
     * 获取季度结束月（1-3月→3月，4-6月→6月，7-9月→9月，10-12月→12月）
     * @param month 输入的月份（如 Month.JANUARY、Month.APRIL 等）
     * @return 对应季度的结束月份（如输入 Month.FEBRUARY，返回 Month.MARCH）
     */
    private static Month getQuarterEndMonth(Month month) {
        // Java 8 不支持多 case 合并（case 1,2,3）和箭头语法，需拆分为独立 case 并显式 return
        switch (month.getValue()) {
            case 1: // 1月属于Q1，结束月为3月
                return Month.MARCH;
            case 2: // 2月属于Q1，结束月为3月
                return Month.MARCH;
            case 3: // 3月属于Q1，结束月为3月
                return Month.MARCH;
            case 4: // 4月属于Q2，结束月为6月
                return Month.JUNE;
            case 5: // 5月属于Q2，结束月为6月
                return Month.JUNE;
            case 6: // 6月属于Q2，结束月为6月
                return Month.JUNE;
            case 7: // 7月属于Q3，结束月为9月
                return Month.SEPTEMBER;
            case 8: // 8月属于Q3，结束月为9月
                return Month.SEPTEMBER;
            case 9: // 9月属于Q3，结束月为9月
                return Month.SEPTEMBER;
            case 10: // 10月属于Q4，结束月为12月
                return Month.DECEMBER;
            case 11: // 11月属于Q4，结束月为12月
                return Month.DECEMBER;
            case 12: // 12月属于Q4，结束月为12月
                return Month.DECEMBER;
            default:
                // 理论上 Month 枚举值仅 1-12，此分支为兜底，避免异常
                throw new IllegalArgumentException("非法的月份值：" + month.getValue());
        }
    }

    /**
     * 根据不同的周期类型转换为对应的月数
     * 此方法用于将给定的周期类型字符串转换为相应的月数
     * 主要用于处理不同时间周期的转换，以便统一时间单位
     *
     * @param period 周期类型字符串，表示不同的周期类型
     * @return 对应的月数如果输入的周期类型不匹配已知类型，则返回0
     */
    public static int GetPeriodMonth(String period) {
        switch (period) {
            case PERIOD_TYPE_MONTH:
                // 当周期类型为月时，返回1个月
                return 1;
            case PERIOD_TYPE_QUARTER:
                // 当周期类型为季度时，返回3个月
                return 3;
            case PERIOD_TYPE_HALFYEAR:
                // 当周期类型为半年时，返回6个月
                return 6;
            case PERIOD_TYPE_YEAR:
                // 当周期类型为年时，返回12个月
                return 12;
            case PERIOD_TYPE_TWOYEAR:
                // 当周期类型为2年时，返回24个月
                return 24;
            default:
                // 当周期类型不匹配已知类型时，返回0
                return 0;
        }
    }

    /**
     * 获取季度起始月（1-3月→1月，4-6月→4月，7-9月→7月，10-12月→10月）
     * @param month 输入的月份（如 Month.FEBRUARY、Month.MAY 等）
     * @return 对应季度的起始月份（如输入 Month.MAY，返回 Month.APRIL）
     */
    private static Month getQuarterStartMonth(Month month) {
        // 同上述逻辑，拆分为独立 case，每个 case 直接返回对应起始月
        switch (month.getValue()) {
            case 1: // 1月属于Q1，起始月为1月
                return Month.JANUARY;
            case 2: // 2月属于Q1，起始月为1月
                return Month.JANUARY;
            case 3: // 3月属于Q1，起始月为1月
                return Month.JANUARY;
            case 4: // 4月属于Q2，起始月为4月
                return Month.APRIL;
            case 5: // 5月属于Q2，起始月为4月
                return Month.APRIL;
            case 6: // 6月属于Q2，起始月为4月
                return Month.APRIL;
            case 7: // 7月属于Q3，起始月为7月
                return Month.JULY;
            case 8: // 8月属于Q3，起始月为7月
                return Month.JULY;
            case 9: // 9月属于Q3，起始月为7月
                return Month.JULY;
            case 10: // 10月属于Q4，起始月为10月
                return Month.OCTOBER;
            case 11: // 11月属于Q4，起始月为10月
                return Month.OCTOBER;
            case 12: // 12月属于Q4，起始月为10月
                return Month.OCTOBER;
            default:
                throw new IllegalArgumentException("非法的月份值：" + month.getValue());
        }
    }

    /**
     * 获取半年结束月（1-6→6，7-12→12）
     */
    private static Month getHalfYearEndMonth(Month month) {
        return month.getValue() <= 6 ? Month.JUNE : Month.DECEMBER;
    }

    /**
     * 获取半年起始月（1-6→1，7-12→7）
     */
    private static Month getHalfYearStartMonth(Month month) {
        return month.getValue() <= 6 ? Month.JANUARY : Month.JULY;
    }

    /**
     * 计算当前季度的总天数（包含首尾月）
     */
    public static int getDaysInCurrentQuarter(LocalDate date) {
        Month startMonth = getQuarterStartMonth(date.getMonth());
        Month endMonth = getQuarterEndMonth(date.getMonth());
        LocalDate quarterStart = LocalDate.of(date.getYear(), startMonth, 1);
        LocalDate quarterEnd = LocalDate.of(date.getYear(), endMonth, endMonth.length(date.isLeapYear()));
        return getPeriodDays(quarterStart, quarterEnd);
    }

    /**
     * 计算当前半年的总天数（包含首尾月）
     */
    public static int getDaysInCurrentHalfYear(LocalDate date) {
        Month startMonth = getHalfYearStartMonth(date.getMonth());
        Month endMonth = getHalfYearEndMonth(date.getMonth());
        LocalDate halfYearStart = LocalDate.of(date.getYear(), startMonth, 1);
        LocalDate halfYearEnd = LocalDate.of(date.getYear(), endMonth, endMonth.length(date.isLeapYear()));
        return getPeriodDays(halfYearStart, halfYearEnd);
    }


    /**
     * 计算日期区间的比例值
     * @param start 起始日期
     * @param end 结束日期
     * @param use30DaysPerMonth 是否按每月30天计算（true=固定30天，false=实际天数）
     * @return 比例值（起始月剩余比例 + 中间完整月数 + 结束月已过比例）
     */
    public static BigDecimal getDateRate(LocalDate start, LocalDate end, boolean use30DaysPerMonth) {
        // 校验日期合法性：start不能晚于end
        if (start.isAfter(end)) {
            return BigDecimal.ZERO; // 或抛出异常
        }

        int startYear = start.getYear();
        int startMonth = start.getMonthValue();
        int endYear = end.getYear();
        int endMonth = end.getMonthValue();

        // 情况1：start和end在同一个月
        if (startYear == endYear && startMonth == endMonth) {
            int totalDaysInMonth = use30DaysPerMonth ? 30 : start.lengthOfMonth();
            int daysBetween = end.getDayOfMonth() - start.getDayOfMonth() + 1; // 包含首尾
            return BigDecimal.valueOf(daysBetween)
                    .divide(BigDecimal.valueOf(totalDaysInMonth), 10, RoundingMode.HALF_UP);
        }

        // 情况2：start和end在不同月份
        // 1. 起始月剩余比例：（当月剩余天数）÷ 当月总天数
        int startMonthTotalDays = use30DaysPerMonth ? 30 : start.lengthOfMonth();
        int startRemainingDays = startMonthTotalDays - start.getDayOfMonth() + 1; // 包含start当天
        BigDecimal startRatio = BigDecimal.valueOf(startRemainingDays)
                .divide(BigDecimal.valueOf(startMonthTotalDays), 10, RoundingMode.HALF_UP);

        // 2. 中间完整月数：start的下一个月 到 end的上一个月之间的完整月份
        int fullMonths = 0;
        int currentYear = startYear;
        int currentMonth = startMonth + 1;
        if (currentMonth > 12) { // 处理跨年（如12月→次年1月）
            currentMonth = 1;
            currentYear++;
        }
        int endPrevYear = endYear;
        int endPrevMonth = endMonth - 1;
        if (endPrevMonth < 1) { // 处理跨年（如1月→上年12月）
            endPrevMonth = 12;
            endPrevYear--;
        }
        // 累加中间完整月数
        while (currentYear < endPrevYear ||
                (currentYear == endPrevYear && currentMonth <= endPrevMonth)) {
            fullMonths++;
            currentMonth++;
            if (currentMonth > 12) {
                currentMonth = 1;
                currentYear++;
            }
        }

        // 3. 结束月已过比例：（当月已过天数）÷ 当月总天数
        int endMonthTotalDays = use30DaysPerMonth ? 30 : end.lengthOfMonth();
        int endPassedDays = end.getDayOfMonth(); // 从1日到end当天的天数
        BigDecimal endRatio = BigDecimal.valueOf(endPassedDays)
                .divide(BigDecimal.valueOf(endMonthTotalDays), 10, RoundingMode.HALF_UP);

        // 总和：起始比例 + 完整月数 + 结束比例
        return startRatio.add(BigDecimal.valueOf(fullMonths)).add(endRatio);
    }

    // ------------------------------ 周期模型：公开静态内部类（可外部访问） ------------------------------

    /**
     * 周期模型：存储周期起止日期、费率、周期ID
     */
    public static class Period {
        private LocalDate periodBeginDate;
        private LocalDate periodEndDate;
        private BigDecimal rate;
        private String commissionPeriodId;

        // Getter & Setter
        public LocalDate getPeriodBeginDate() {
            return periodBeginDate;
        }

        public void setPeriodBeginDate(LocalDate periodBeginDate) {
            this.periodBeginDate = periodBeginDate;
        }

        public LocalDate getPeriodEndDate() {
            return periodEndDate;
        }

        public void setPeriodEndDate(LocalDate periodEndDate) {
            this.periodEndDate = periodEndDate;
        }

        public BigDecimal getRate() {
            return rate;
        }

        public void setRate(BigDecimal rate) {
            this.rate = rate;
        }

        public String getCommissionPeriodId() {
            return commissionPeriodId;
        }

        public void setCommissionPeriodId(String commissionPeriodId) {
            this.commissionPeriodId = commissionPeriodId;
        }

        // 格式化日期字符串（避免外部重复创建Formatter）
        public String getPeriodBeginDateStr() {
            return periodBeginDate.format(DATE_FORMATTER);
        }

        public String getPeriodEndDateStr() {
            return periodEndDate.format(DATE_FORMATTER);
        }

        public String getPeriodMonthStr() {
            return periodBeginDate.format(MONTH_FORMATTER);
        }
    }
}