package com.awspaas.user.apps.donenow_ctt;

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;

/**
 * 周期计算工具类
 * 支持：月/季/半年/年周期、自然月/实际月模式、周期列表生成与费率计算
 */
public class PeriodCalculationUtil {
    // 周期类型常量（与原IVTConstant对齐，可根据实际常量类调整）
    public static final String PERIOD_TYPE_ONCE = "ONCE";       // 一次性收费
    public static final String PERIOD_TYPE_MONTH = "MONTH";     // 按月支付
    public static final String PERIOD_TYPE_QUARTER = "QUARTER"; // 按季支付
    public static final String PERIOD_TYPE_HALFYEAR = "HALFYEAR"; // 按半年支付
    public static final String PERIOD_TYPE_YEAR = "YEAR";       // 按年支付

    // 日期格式化器（复用避免重复创建）
    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("工具类不允许实例化");
    }

    /**
     * 核心方法：生成周期列表（含费率）
     *
     * @param ruleCate          周期类型（参考类内PERIOD_TYPE_*常量）
     * @param contractStartDate 合同开始日期
     * @param periodBeginDate   服务开始日期（周期计算起始点）
     * @param periodEndDate     服务结束日期（周期计算终点）
     * @param isNaturalMonth    是否自然月模式（true=自然月，false=实际月）
     * @return 周期列表（含起止日期、费率）
     * @throws IllegalArgumentException 当参数非法时抛出（如日期null、周期类型无效）
     */
    public static List<Period> getPeriodList(
            String ruleCate,
            LocalDate contractStartDate,
            LocalDate periodBeginDate,
            LocalDate periodEndDate,
            boolean isNaturalMonth
    ) {
        // 1. 参数校验：避免空指针和非法参数
        validateParams(ruleCate, contractStartDate, periodBeginDate, periodEndDate);

        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;
        }

        // 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;

            default:
                throw new IllegalArgumentException("不支持的周期类型：" + ruleCate);
        }

        // 3.2 处理首个周期
        if (currLastDay != null) {
            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)) {
                    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);
            }
        }

        return periodList;
    }

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

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

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

    /**
     * 自然月：获取当前月的最后一天（处理非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).minusDays(1);
        } else {
            // 合同开始日 = 服务开始日：首个周期为1个月后
            endDay = contractStartDate.plusMonths(1).minusDays(1);
        }
        // 若结束日超过服务结束日，截断
        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).minusDays(1);
        } else {
            endDay = contractStartDate.plusMonths(3).minusDays(1);
        }
        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).minusDays(1);
        } else {
            endDay = contractStartDate.plusMonths(6).minusDays(1);
        }
        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).minusDays(1);
        } else {
            endDay = contractStartDate.plusMonths(12).minusDays(1);
        }
        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);
            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);
    }

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

    /**
     * 获取季度结束月（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());
        }
    }

    /**
     * 获取季度起始月（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);
    }

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

    /**
     * 周期模型：存储周期起止日期、费率、周期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);
        }
    }
}