package com.cf.imes.module.system.service.pay;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.pay.config.ChenfengPayConfig;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductProcessorRespVO;
import com.cf.imes.module.system.dal.dataobject.funds.productpromotion.ProductPromotionDO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO;
import com.cf.imes.module.system.enums.pay.DurationUnitEnum;
import com.cf.imes.module.system.enums.pay.InvoiceStatusEnum;
import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum;
import com.cf.imes.module.system.enums.pay.PayOrderStatusEnum;
import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum;
import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum;
import com.cf.imes.module.system.enums.pay.TradeTypeEnum;
import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties;
import com.cf.imes.module.system.service.pay.dto.DateIntervalDTO;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR;

/**
 * 产品购买类型处理器
 *
 * @author Gqr
 * @since 2025/8/14 10:54
 */
public class PayProductProcessorNew {

    // 当次订购的产品
    private ProductsDO productsDO;

    // 当次订购的定价规则
    private ProductsDetailDO productsDetailDO;

    // 组织产品的首次订购记录
    private PurchaseRecordDO orgInitialPurchaseRecord;

    // 组织id
    private Long organId;

    // 支付配置
    private ChenfengPayConfig payConfig;

    // id生成规则配置
    private MybatisIdProperties mybatisIdProperties;

    // 前序购买记录的延期记录
    private List<ProductDelayRespVO> delayRespVOList;

    // 前序购买记录
    private PurchaseRecordDO previousPurchaseRecord;

    // 本地订购产品的开始时间
    private LocalDate startTime = LocalDate.now();

    // 产品结束时间
    private LocalDate endTime;

    // 本地订购产品的月均单价
    private BigDecimal monthAverageAmount = BigDecimal.ZERO;

    // 原始价格
    private BigDecimal originalPrice;

    // 前序购买记录的id
    private Long previousPurchaseRecordId;

    // 产品支付价格
    private BigDecimal productPrice;

    // 按序流转价格
    private BigDecimal price;

    // 支付类型
    private Integer payType;

    // 当次购买记录DO
    private PurchaseRecordDO purchaseRecordDO;

    // 当次支付订单DO
    private PayOrderDO payOrderDO;

    // 优惠活动id
    private Long productPromotionId;

    // 赠送时长
    private Integer giftDuration;

    // 赠送时长单位
    private Integer giftDurationUnit;

    // 可用的产品优惠活动列表
    private List<ProductPromotionDO> availableProductPromotionList;

    public PayProductProcessorNew(ProductsDO productsDO, ProductsDetailDO productsDetailDO, PurchaseRecordDO orgInitialPurchaseRecord, PreviouProductDelayRespVO previouProductDelayRespVO,
                                  List<ProductPromotionDO> availableProductPromotionList, Long organId, ChenfengPayConfig payConfig, MybatisIdProperties mybatisIdProperties) {
        this.productsDO = productsDO;
        this.productsDetailDO = productsDetailDO;
        this.orgInitialPurchaseRecord = orgInitialPurchaseRecord;
        this.availableProductPromotionList = availableProductPromotionList;
        if (ObjectUtil.isNotNull(previouProductDelayRespVO.getPreviouPurchaseRecord())) {
            delayRespVOList = previouProductDelayRespVO.getDelayRespVOList();
            previousPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord();
            previousPurchaseRecordId = previousPurchaseRecord.getId();
            // 上一次订购产品的结束时间进行计算
            endTime = previousPurchaseRecord.getEndTime();
            endTime = calculateEndTime(productsDetailDO.getDuration(), productsDetailDO.getDurationUnit());
        } else {
            // 首次订购正常从今天开始累加算世界时间
            endTime = addTime(startTime, productsDetailDO.getDuration(), productsDetailDO.getDurationUnit());
        }
        this.organId = organId;
        this.payConfig = payConfig;
        this.mybatisIdProperties = mybatisIdProperties;
    }

    /**
     * 根据产品支付类型解析出对应的信息，
     *
     * @param payType
     */
    public ProductProcessorRespVO processPayment(Integer payType) {
        // 首次购买记录存在则需要做计算，否则直接用定价规则的现价
        boolean initialPurchaseExist = ObjectUtil.isNotNull(orgInitialPurchaseRecord);
        if (initialPurchaseExist) {
            price = computePrice();
            productPrice = price;
        } else {
            price = computeInitialProductPayAmount();
            productPrice = price;
        }
        this.payType = payType;

        // 三方支付
        payByExternal();

        // 根据扣款情况生成购买记录和收支记录
        generatePurchaseRecordAndIncomeRecord();

        return new ProductProcessorRespVO(purchaseRecordDO, payOrderDO, delayRespVOList);
    }

    /**
     * 根据扣款情况生成购买记录和收支记录
     */
    private void generatePurchaseRecordAndIncomeRecord() {
        // 新增购买记录
        long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId();
        purchaseRecordDO = PurchaseRecordDO.builder()
                .id(purchaseId)
                .organId(organId)
                .productId(productsDO.getId())
                .productName(productsDO.getProductName())
                .productDetailsId(productsDetailDO.getId())
                .purchaseDuration(productsDetailDO.getDuration())
                .purchaseDurationUnit(productsDetailDO.getDurationUnit())
                .giftDuration(giftDuration)
                .giftDurationUnit(giftDurationUnit)
                .startTime(startTime)
                .endTime(endTime)
                .paymentMethod(payType)
                .totalAmount(productPrice)
                .originalAmount(originalPrice)
                .monthAverageAmount(monthAverageAmount)
                .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
                .status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
                .previousId(previousPurchaseRecordId)
                .productPromotionId(productPromotionId)
                .build();

        // 第三方支付在异步回调后创建收支记录
        payOrderDO.setPurchaseId(purchaseId);

        // 记录第三方支付金额
        purchaseRecordDO.setExternalSpent(price);
        // 购买记录为等待支付
        purchaseRecordDO.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus());
        // 更新为不可开票，等异步回调后修改为可开票
        purchaseRecordDO.setIsInvocing(null);

    }

    /**
     * 创建第三方支付订单
     */
    private void payByExternal() {
        payOrderDO = new PayOrderDO();

        Long payOrderId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId();

        payOrderDO.setId(payOrderId);
        payOrderDO.setStatus(PayOrderStatusEnum.WAITING);
        payOrderDO.setExpireTime(LocalDateTime.now().plus(payConfig.getPayExpireTime()));
        payOrderDO.setPrice(price);
        payOrderDO.setSubject(productsDO.getProductName());
        payOrderDO.setOrganId(organId);
        payOrderDO.setTradeType(TradeTypeEnum.PRODUCT.getCode());
        if (PayChannelCodeEnum.isAlipay(payType)) {
            payOrderDO.setChannelCode(PayChannelCodeEnum.ALIPAY_PC);
        } else if (PayChannelCodeEnum.isWechat(payType)) {
            payOrderDO.setChannelCode(PayChannelCodeEnum.WECHAT_NATIVE);
        }
    }

    /**
     * 获取订购产品的到期时间
     *
     * @return
     */
    public LocalDate getProductPayExpireTime() {
        return endTime;
    }

    /**
     * 计算订购后的结束时间
     *
     * 规则：
     * 1、已过期：从今天开始计算
     * 2、未过期：从当前结束时间开始叠加
     * 3、扣减已经消耗的延期天数（未来延期不扣）
     * 4、叠加优惠活动赠送时长
     */
    private LocalDate calculateEndTime(Integer duration, Integer durationUnit) {
        LocalDate today = LocalDate.now();

        // 1、计算续费起点
        LocalDate purchaseStart;
        if (today.isAfter(endTime)) {
            // 已过期，从今天开始
            purchaseStart = today;
        } else {
            // 未过期，从当前结束时间开始
            purchaseStart = endTime;
        }

        // 2、增加购买时长
        if (ObjectUtil.isNotNull(duration) && durationUnit != null) {
            endTime = addTime(purchaseStart, duration, durationUnit);
        }

        // 3、扣除已经消耗的延期天数
        long delayDays = 0L;
        if (CollUtil.isNotEmpty(delayRespVOList)) {
            delayDays = calculateDelayDays(startTime, delayRespVOList);
        }

        if (delayDays > 0) {
            endTime = endTime.minusDays(delayDays);
        }

        // 4、累加活动赠送时间
        if (CollUtil.isNotEmpty(availableProductPromotionList)) {
            calculateGiftDuration(duration, durationUnit);
        }

        // 校验扣减完毕后是不是还是过期了，那么就是订购时间无法覆盖延期扣减
        if (endTime.isBefore(today)) {
            throw new ServiceException(PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR, LocalDateTimeUtil.format(endTime, DatePattern.NORM_DATE_PATTERN));
        }

        return endTime;
    }

    /**
     * 计算优惠活动赠送时长
     *
     * @param duration
     * @param durationUnit
     */
    private void calculateGiftDuration(Integer duration, Integer durationUnit) {
        Optional<ProductPromotionDO> availableProductPromotionOpt = availableProductPromotionList.stream()
                .filter(p -> duration.equals(p.getPurchaseDuration()) && durationUnit.equals(p.getPurchaseDurationUnit()))
                .findFirst();

        if (availableProductPromotionOpt.isPresent()) {
            ProductPromotionDO productPromotionDO = availableProductPromotionOpt.get();
            productPromotionId = productPromotionDO.getId();
            giftDuration = productPromotionDO.getGiftDuration();
            giftDurationUnit = productPromotionDO.getGiftDurationUnit();
            endTime = addTime(endTime, giftDuration, giftDurationUnit);
        }
    }

    /**
     * 计算总延期天数（合并区间，避免重叠/不连续）
     */
    private static long calculateDelayDays(LocalDate startTime, List<ProductDelayRespVO> delayRecords) {
        // 1. 生成延期区间并打上备注
        List<DateIntervalDTO> intervals = buildIntervals(startTime, delayRecords);
        if (CollUtil.isEmpty(intervals)) {
            return 0L;
        }

        // 2. 合并区间
        List<DateIntervalDTO> merged = mergeIntervals(intervals);

        // 3. 计算天数
        return calculateTotalDays(merged);
    }


    /**
     * 根据延期记录生成扣减区间，同时写入备注
     */
    private static List<DateIntervalDTO> buildIntervals(LocalDate startTime, List<ProductDelayRespVO> delayRecords) {
        List<DateIntervalDTO> intervals = new ArrayList<>();

        for (ProductDelayRespVO record : delayRecords) {
            // 延期开始日期（原结束日期的下一天）
            LocalDate delayStart = record.getEndTime().plusDays(1);
            // 延期结束日期
            LocalDate delayEnd = record.getDelayTime();

            // 异常数据
            if (delayEnd.isBefore(delayStart)) {
                record.setRemark("无扣减");
                continue;
            }

            // 延期尚未开始
            if (startTime.isBefore(delayStart)) {
                record.setRemark("无扣减");
                continue;
            }

            LocalDate intervalEnd;
            if (startTime.isAfter(delayEnd)) {
                // 情况1：延期结束在计算结束之前 → 整段都扣
                intervalEnd = delayEnd;
            } else {
                // 情况2：延期结束晚于计算结束 → 扣到 startTime 为止
                intervalEnd = startTime;
            }

            long deductDays = ChronoUnit.DAYS.between(delayStart, intervalEnd) + 1;
            if (deductDays > 0) {
                record.setRemark("扣减" + deductDays + "天");
                intervals.add(new DateIntervalDTO(delayStart, intervalEnd));
            } else {
                record.setRemark("无扣减");
            }
        }

        return intervals;
    }

    /**
     * 合并区间（避免重叠/相邻）
     */
    private static List<DateIntervalDTO> mergeIntervals(List<DateIntervalDTO> intervals) {
        intervals.sort(Comparator.comparing(DateIntervalDTO::getStart));

        List<DateIntervalDTO> merged = new ArrayList<>();
        DateIntervalDTO current = intervals.get(0);

        for (int i = 1; i < intervals.size(); i++) {
            DateIntervalDTO next = intervals.get(i);
            if (!next.getStart().isAfter(current.getEnd().plusDays(1))) {
                // 重叠或相邻 → 合并
                current.setEnd(current.getEnd().isAfter(next.getEnd()) ? current.getEnd() : next.getEnd());
            } else {
                merged.add(current);
                current = next;
            }
        }
        merged.add(current);

        return merged;
    }

    /**
     * 计算合并区间的总天数
     */
    private static long calculateTotalDays(List<DateIntervalDTO> merged) {
        long totalDays = 0L;
        for (DateIntervalDTO interval : merged) {
            totalDays += ChronoUnit.DAYS.between(interval.getStart(), interval.getEnd()) + 1;
        }
        return totalDays;
    }

    /**
     * 基于时长和单位累加time
     *
     * @param time
     * @param duration
     * @param unit
     * @return
     */
    private static LocalDate addTime(LocalDate time, int duration, int unit) {
        switch (DurationUnitEnum.fromStatus(unit)) {
            case YEAR:
                return time.plusYears(duration);
            case MONTH:
                return time.plusMonths(duration);
            case DAY:
                return time.plusDays(duration);
            default:
                return time;
        }
    }

    /**
     * 计算有首购记录的产品价格
     *
     * @return
     */
    private BigDecimal computePrice() {
        // 月均单价
        monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount();

        // 首购的定价
        BigDecimal initialPurchaseRecordTotalAmount = orgInitialPurchaseRecord.getTotalAmount();

        // 定价规则的折扣
        BigDecimal discount = productsDetailDO.getDiscount();
        if (ObjectUtil.isNull(discount)) {
            discount = BigDecimal.valueOf(100);
        }

        BigDecimal totalAmount;
        /**
         * ①：续费订购满1年：
         *     原始价格 = 订购总价 * duration
         *     现价 = 原始价格 * 折扣 /100，保留两位小数
         * ②：不满1年按月份计算
         *     原始价格 = 月均单价 * duration
         *     现价 = 原始价格 * 折扣 /100，保留两位小数
         */
        switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) {
            case YEAR -> {
                originalPrice = initialPurchaseRecordTotalAmount
                        .multiply(BigDecimal.valueOf(productsDetailDO.getDuration()));
                totalAmount = originalPrice
                        .multiply(discount)
                        .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
            }
            case MONTH -> {
                originalPrice = monthAverageAmount
                        .multiply(BigDecimal.valueOf(productsDetailDO.getDuration()));
                totalAmount = originalPrice
                        .multiply(discount)
                        .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
            }
            default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
        }

        return totalAmount;
    }

    /**
     * 计算首次订购的价格
     */
    private BigDecimal computeInitialProductPayAmount() {
        // 订购数量
        Integer duration = productsDetailDO.getDuration();
        // 产品订购价格
        BigDecimal orgPurchasePrice = productsDO.getPrice();
        // 折扣
        BigDecimal discount = productsDetailDO.getDiscount();
        if (ObjectUtil.isNull(discount)) {
            discount = BigDecimal.valueOf(100);
        }

        BigDecimal totalAmount;
        switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) {
            case YEAR -> {
                // 原价
                originalPrice = orgPurchasePrice.multiply(BigDecimal.valueOf(duration));
                // 折扣价格 = 原价 * 折扣
                totalAmount = originalPrice
                        .multiply(discount)
                        .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
                // 月均单价 = 折扣价格 / 10
                monthAverageAmount = originalPrice.divide(new BigDecimal(10), 2, RoundingMode.DOWN);
            }
            case MONTH -> {
                // 月均单价 = 订购价格 / 10
                monthAverageAmount = orgPurchasePrice.divide(new BigDecimal(10), 2, RoundingMode.DOWN);
                // 原价
                originalPrice = monthAverageAmount.multiply(BigDecimal.valueOf(duration));
                // 折扣价格 = 原价 * 折扣
                totalAmount = originalPrice.multiply(discount)
                        .divide(new BigDecimal(100), 2, RoundingMode.DOWN);
            }
            default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
        }
        return totalAmount;
    }

}
