package com.ylx.order.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ylx.common.core.domain.model.WxLoginUser;
import com.ylx.common.exception.ServiceException;
import com.ylx.common.utils.DateUtils;
import com.ylx.common.utils.SecurityUtils;
import com.ylx.message.enums.TriggerEventEnum;
import com.ylx.message.service.IMessageService;
import com.ylx.order.domain.AfterSalesService;
import com.ylx.order.domain.RefundRuleDetail;
import com.ylx.order.domain.TOrder;
import com.ylx.order.domain.dto.AfterSalesServiceDTO;
import com.ylx.order.domain.vo.AfterSalesServiceApplyVO;
import com.ylx.order.domain.vo.RefundCalculationVO;
import com.ylx.order.domain.vo.AfterSalesServiceDetailVO;
import com.ylx.order.domain.vo.RegulationConfigVO;
import com.ylx.order.enums.AfterSaleServiceStatusEnum;
import com.ylx.order.enums.OrderStatusEnum;
import com.ylx.order.enums.RefundStageTypeEnum;
import com.ylx.order.mapper.AfterSalesServiceMapper;
import com.ylx.order.service.IAfterSalesServiceService;
import com.ylx.order.service.RefundRuleDetailService;
import com.ylx.order.service.RegulationService;
import com.ylx.order.service.TOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 售后单服务实现
 *
 * @author xxx
 */
@Slf4j
@Service
public class AfterSalesServiceServiceImpl extends ServiceImpl<AfterSalesServiceMapper, AfterSalesService>
        implements IAfterSalesServiceService {

    @Resource
    private TOrderService tOrderService;
    @Resource
    private RefundRuleDetailService refundRuleDetailService;
    @Resource
    private RegulationService regulationService;
    @Resource
    private IMessageService messageService;

    // ===================== 业务常量 =====================
    /**
     * 订单完成后允许退款最大时长(小时)
     */
    private static final int MAX_REFUND_HOURS = 48;
    /**
     * 售后单号前缀
     */
    private static final String SERVICE_NO_PREFIX = "ASS";

    // ===================== 对外接口 =====================
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitAfterSale(AfterSalesServiceDTO dto) {
        // 1. 登录校验
        WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
        if (ObjectUtil.isNull(wxLoginUser)) {
            log.warn("提交售后失败：用户未登录");
            throw new ServiceException("请先登录");
        }
        Long currentUserId = Long.valueOf(wxLoginUser.getId());

        // 2. 订单存在 & 权限校验
        Long orderId = dto.getOrderId();
        TOrder order = getOrder(orderId);
        if (!currentUserId.equals(order.getUserId())) {
            throw new ServiceException("无权操作他人订单");
        }

        // 3. 前置业务校验（顺序不可乱）
        validateOrderStatus(order);
        validateNoPendingAfterSale(orderId);
        validateNoRefundedAfterSale(orderId);

        // 4. 计算退款金额
        RefundCalculationVO refundResult = calculateRefund(order);

        // 5. 构建售后单并保存
        AfterSalesService afterSalesService = buildAfterSalesService(dto, orderId, currentUserId, refundResult);
        boolean saveSuccess = save(afterSalesService);
        if (!saveSuccess) {
            throw new ServiceException("创建售后单失败，请稍后重试");
        }

        // 6. 发送消息
        Map<String, Object> variables = new HashMap<>();
        variables.put("refundId", afterSalesService.getId());
        this.messageService.sendMessage(currentUserId, TriggerEventEnum.REFUND_PROCESSING.getDesc(), variables);

        log.info("售后提交成功，orderId={}，serviceNo={}，退款金额={}",
                orderId, afterSalesService.getServiceNo(), refundResult.getRefundAmount());
    }

    @Override
    public RefundCalculationVO calculateRefund(AfterSalesServiceDTO dto) {
        Long orderId = dto.getOrderId();
        TOrder order = getOrder(orderId);
        return calculateRefund(order);
    }

    @Override
    public AfterSalesServiceDetailVO getDetailById(Long id) {
        AfterSalesServiceDetailVO detailVO = baseMapper.getDetailById(id);
        if (ObjectUtil.isNull(detailVO)) {
            throw new ServiceException("暂无该售后单详情");
        }
        return detailVO;
    }

    @Override
    public AfterSalesServiceApplyVO applyAfterSale(AfterSalesServiceDTO dto) {

        AfterSalesServiceApplyVO vo = new AfterSalesServiceApplyVO();

        // 1. 获取订单数据
        Long orderId = dto.getOrderId();
        TOrder order = this.getOrder(orderId);

        Integer stageType = resolveRefundStageType(order);

        // 2. 根据订单的流转状态获取退款说明
        List<RegulationConfigVO> descListByExecStatus = regulationService.getDescListByExecStatus(stageType);
        vo.setRefundRules(descListByExecStatus);

        // 3. 设置产品信息
        vo.setProductName(order.getProjectName());
        vo.setProductPrice(order.getFinalAmount());

        // 3. 根据订单的流转状态获取退款金额
        RefundCalculationVO refundCalculationVO = this.calculateRefund(dto);
        if(ObjectUtil.isNotNull(refundCalculationVO)){
            vo.setRefundAmount(refundCalculationVO.getRefundAmount());
        }

        return vo;
    }

    private TOrder getOrder(Long orderId) {
        TOrder order = tOrderService.getById(orderId);
        if (ObjectUtil.isNull(order) || ObjectUtil.equals(1, order.getIsDelete())) {
            throw new ServiceException("订单不存在");
        }
        return order;
    }

    // ===================== 校验类私有方法 =====================

    /**
     * 校验订单状态是否允许发起售后
     */
    private void validateOrderStatus(TOrder order) {
        Integer orderStatus = order.getStatus();
        OrderStatusEnum statusEnum = OrderStatusEnum.fromCode(orderStatus);

        switch (statusEnum) {
            case IN_SERVICE:
                log.info("订单[{}]服务中，禁止发起售后", order.getId());
                throw new ServiceException("操作错误，请重试");
            case CANCELLED:
                log.info("订单[{}]已取消，禁止发起售后", order.getId());
                throw new ServiceException("操作错误，请重试");
            case COMPLETED:
                validateCompletedRefundTime(order);
                break;
            default:
                // 其他状态放行
        }
    }

    /**
     * 校验已完成订单退款时效
     */
    private void validateCompletedRefundTime(TOrder order) {
        LocalDateTime completedTime = order.getCompletedTime();
        if (ObjectUtil.isNull(completedTime)) {
            throw new ServiceException("订单完成时间异常");
        }
        long passHours = Duration.between(completedTime, LocalDateTime.now()).toHours();
        if (passHours > MAX_REFUND_HOURS) {
            log.info("订单[{}]已完成超过{}小时，禁止退款", order.getId(), MAX_REFUND_HOURS);
            throw new ServiceException("已完成超过48小时的订单不支持退款");
        }
    }

    /**
     * 校验：不存在【待审核、已同意、已退款】等进行中的售后单
     */
    private void validateNoPendingAfterSale(Long orderId) {
        List<Integer> pendingStatus = Arrays.asList(
                AfterSaleServiceStatusEnum.PENDING_AUDIT.getCode(),
                AfterSaleServiceStatusEnum.APPROVED.getCode()
        );

        long pendingCount = lambdaQuery()
                .eq(AfterSalesService::getOrderId, orderId)
                .in(AfterSalesService::getStatus, pendingStatus)
                .count();

        if (pendingCount > 0) {
            throw new ServiceException("该订单已有处理中的售后单，请勿重复提交");
        }
    }

    /**
     * 校验：该订单不存在【已退款】售后单
     */
    private void validateNoRefundedAfterSale(Long orderId) {
        long refundedCount = lambdaQuery()
                .eq(AfterSalesService::getOrderId, orderId)
                .eq(AfterSalesService::getStatus, AfterSaleServiceStatusEnum.REFUND_SUCCESS.getCode())
                .count();

        if (refundedCount > 0) {
            log.info("订单[{}]已存在已退款售后单，禁止再次申请", orderId);
            throw new ServiceException("该订单已完成退款，无法再次申请");
        }
    }

    // ===================== 退款计算核心 =====================
    private RefundCalculationVO calculateRefund(TOrder order) {
        Integer stageType = resolveRefundStageType(order);
        List<RefundRuleDetail> ruleList = listRefundRuleByStage(stageType);

        if (CollUtil.isEmpty(ruleList)) {
            throw new ServiceException("暂无可用退款规则，请联系客服");
        }

        RefundRuleDetail matchRule;
        if (RefundStageTypeEnum.PRE_DEPARTURE.getCode().equals(stageType)) {
            matchRule = matchPreDepartureRule(ruleList, order.getAppointmentStartTime());
        } else {
            matchRule = CollUtil.getFirst(ruleList);
        }

        BigDecimal refundPercent = Optional.ofNullable(matchRule.getRefundPercent()).orElse(BigDecimal.ZERO);
        BigDecimal orderFinalAmount = Optional.ofNullable(order.getFinalAmount()).orElse(BigDecimal.ZERO);
        BigDecimal refundAmount = orderFinalAmount
                .multiply(refundPercent)
                .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);

        return new RefundCalculationVO(refundAmount, matchRule.getRefundDesc());
    }

    /**
     * 解析当前退款阶段类型
     */
    private Integer resolveRefundStageType(TOrder order) {
        if (OrderStatusEnum.IN_SERVICE.getCode().equals(order.getStatus())) {
            return RefundStageTypeEnum.IN_SERVICE.getCode();
        }
        return ObjectUtil.defaultIfNull(order.getExecStatus(), RefundStageTypeEnum.PRE_DEPARTURE.getCode());
    }

    /**
     * 根据阶段查询退款规则
     */
    private List<RefundRuleDetail> listRefundRuleByStage(Integer stageType) {
        LambdaQueryWrapper<RefundRuleDetail> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(RefundRuleDetail::getStageType, stageType)
                .eq(RefundRuleDetail::getIsDelete, 0)
                .orderByAsc(RefundRuleDetail::getSortOrder)
                .orderByAsc(RefundRuleDetail::getId);
        return refundRuleDetailService.list(wrapper);
    }

    /**
     * 匹配未出发阶段退款规则
     */
    private RefundRuleDetail matchPreDepartureRule(List<RefundRuleDetail> details, LocalDateTime appointmentStartTime) {
        details.sort(Comparator.comparing(RefundRuleDetail::getSortOrder, Comparator.nullsLast(Integer::compareTo)));
        RefundRuleDetail firstRule = CollUtil.getFirst(details);

        if (details.size() == 1 && ObjectUtil.equals(0, firstRule.getRefundType())) {
            return firstRule;
        }

        BigDecimal hoursLeft = calcHoursUntilAppointStart(appointmentStartTime);
        if (hoursLeft.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("已过预约服务时间，无法退款");
        }

        for (RefundRuleDetail rule : details) {
            if (matchTimeRangeRule(rule, hoursLeft)) {
                return rule;
            }
        }
        throw new ServiceException("未匹配到对应退款规则");
    }

    /**
     * 校验是否命中时间区间规则
     */
    private boolean matchTimeRangeRule(RefundRuleDetail detail, BigDecimal hoursLeft) {
        BigDecimal timeStart = detail.getTimeStartHours();
        BigDecimal timeEnd = detail.getTimeEndHours();

        if (ObjectUtil.isNotNull(timeStart) && timeStart.compareTo(BigDecimal.ZERO) > 0) {
            return hoursLeft.compareTo(timeEnd) > 0 && hoursLeft.compareTo(timeStart) <= 0;
        }
        if (ObjectUtil.isNotNull(timeEnd)) {
            return hoursLeft.compareTo(timeEnd) <= 0;
        }
        return false;
    }

    /**
     * 计算距离预约开始剩余小时数
     */
    private BigDecimal calcHoursUntilAppointStart(LocalDateTime appointmentStartTime) {
        if (ObjectUtil.isNull(appointmentStartTime)) {
            throw new ServiceException("订单预约时间缺失，无法计算退款");
        }
        long minutes = Duration.between(LocalDateTime.now(), appointmentStartTime).toMinutes();
        return BigDecimal.valueOf(minutes)
                .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
    }

    // ===================== 构建 & 工具方法 =====================

    /**
     * 组装售后单实体
     */
    private AfterSalesService buildAfterSalesService(AfterSalesServiceDTO dto, Long orderId,
                                                     Long userId, RefundCalculationVO refundVO) {
        AfterSalesService service = new AfterSalesService();
        service.setServiceNo(generateServiceNo(orderId));
        service.setOrderId(orderId);
        service.setUserId(userId);
        service.setStatus(AfterSaleServiceStatusEnum.PENDING_AUDIT.getCode());
        service.setActualRefundAmount(refundVO.getRefundAmount());
        service.setRefundDesc(refundVO.getRefundDesc());
        service.setCreateTime(DateUtils.getNowDate());
        service.setRemark(dto.getRemark());
        service.setReason(dto.getReason());
        return service;
    }

    /**
     * 生成唯一售后单号（高可用，杜绝重复）
     */
    private String generateServiceNo(Long orderId) {
        // 前缀 + 订单ID + 时间戳 + 短UUID，全局唯一
        return SERVICE_NO_PREFIX + orderId + System.currentTimeMillis() + UUID.fastUUID().toString(true).substring(0, 6);
    }

}