package com.jeesharp.business.modules.mall.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.mall.dto.MallConfirmPayDTO;
import com.jeesharp.business.modules.mall.entity.MallOrder;
import com.jeesharp.business.modules.mall.entity.MallOrderItem;
import com.jeesharp.business.modules.mall.enums.MallDeliveryTypeEnum;
import com.jeesharp.business.modules.mall.enums.MallOrderStatusEnum;
import com.jeesharp.business.modules.mall.enums.MallPayTypeEnum;
import com.jeesharp.business.modules.mall.mapper.MallOrderMapper;
import com.jeesharp.business.modules.mall.service.MallOrderItemService;
import com.jeesharp.business.modules.mall.service.MallOrderService;
import com.jeesharp.business.modules.mall.service.MallSkuService;
import com.jeesharp.business.modules.mall.vo.MallOrderExportVO;
import com.jeesharp.business.modules.mall.vo.MallOrderQueryVO;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.mybatis.core.service.CrudService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

/**
 * 商城订单领域服务：持久化、库存、预支付参数组装与支付确认。
 *
 * <p>与 Application 的分工：本类只管 {@code mall_order} 表状态与库存；优惠券冻结/核销由
 * {@code CouponCheckoutApi} 在 Application 层编排；微信统一下单在网关
 * {@code WechatJsapiPayOrderPrepayService}。</p>
 *
 * @author jeesharp
 * @since 2026-05-08
 */
@Service
@Transactional(readOnly = true)
public class MallOrderServiceImpl extends CrudService<MallOrderMapper, MallOrder, MallOrderQueryVO> implements MallOrderService {

    private static final String IDS_SEPARATOR = ",";
    private static final String ORDER_NOT_FOUND_MESSAGE = "订单不存在";
    private static final String ORDER_MEMBER_MISMATCH_MESSAGE = "无权操作该订单";
    private static final String INVALID_PAY_PREPARE_STATUS_MESSAGE = "当前状态不可发起支付";
    private static final String INVALID_PAY_CONFIRM_STATUS_MESSAGE = "当前状态不可确认支付";
    private static final String PAY_AMOUNT_INVALID_MESSAGE = "支付金额必须大于0";
    private static final String LOGIN_REQUIRED_MESSAGE = "请先登录";
    private static final String MALL_ORDER_LINE_EMPTY_MESSAGE = "订单商品不能为空";
    private static final String MALL_ORDER_ID_EMPTY_MESSAGE = "订单创建失败";
    private static final String INVALID_PICKUP_VERIFY_MESSAGE = "核销失败，请核对核销码与订单状态";
    private static final String PICKUP_ALREADY_VERIFIED_MESSAGE = "该订单已核销";
    private static final String PICKUP_VERIFY_NOT_PICKUP_MESSAGE = "非自提订单，无法核销";
    private static final String ORDER_NOT_DELIVERY_MESSAGE = "非快递订单，无需发货";
    private static final String INVALID_SHIP_STATUS_MESSAGE = "当前订单状态不可发货";
    private static final String LOGISTICS_COMPANY_REQUIRED_MESSAGE = "请选择或填写物流公司";
    private static final String LOGISTICS_NO_REQUIRED_MESSAGE = "运单号不能为空";
    private static final String INVALID_CANCEL_STATUS_MESSAGE = "当前状态不可取消";
    private static final int PICKUP_VERIFY_CODE_LEN = 10;
    private static final int PICKUP_VERIFY_CODE_RETRY = 8;

    @Resource
    private MallOrderItemService mallOrderItemService;
    @Resource
    private MallSkuService mallSkuService;

    @Override
    @Transactional(readOnly = false)
    public void createOrder(MallOrder order) {
        save(order);
    }

    @Override
    @Transactional(readOnly = false)
    public void updateOrder(MallOrder order) {
        update(order);
    }

    @Override
    public List<MallOrderExportVO> findExportList(MallOrderQueryVO query) {
        dataRuleFilter(query);
        return getBaseMapper().selectExportList(query);
    }

    /**
     * 创建待支付订单：写主单 + 明细，并立即扣减 SKU 库存（取消/超时会 increaseStock 回滚）。
     */
    @Override
    @Transactional(readOnly = false)
    public void createPendingMobileOrder(MallOrder order, List<MallOrderItem> items) {
        if (items == null || items.isEmpty()) {
            throw new BizErrorException(MALL_ORDER_LINE_EMPTY_MESSAGE);
        }
        sealMerchantTenant(order);
        createOrder(order);
        if (StrUtil.isBlank(order.getId())) {
            throw new BizErrorException(MALL_ORDER_ID_EMPTY_MESSAGE);
        }
        for (MallOrderItem it : items) {
            it.setOrderId(order.getId());
            it.setTenantCode(order.getTenantCode());
            it.setTenantId(order.getTenantId());
            sealMerchantTenant(it);
            mallOrderItemService.createItem(it);
            mallSkuService.decreaseStock(it.getSkuId(), it.getQuantity());
        }
    }

    /**
     * 校验订单是否可发起预支付。
     */
    @Override
    public void assertPayPreparable(MallOrder order, String memberId) {
        if (StrUtil.isBlank(memberId)) {
            throw new BizErrorException(LOGIN_REQUIRED_MESSAGE);
        }
        if (order == null) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        if (!memberId.equals(order.getMemberId())) {
            throw new BizErrorException(ORDER_MEMBER_MISMATCH_MESSAGE);
        }
        if (!MallOrderStatusEnum.PENDING_PAY.getCode().equals(order.getStatus())) {
            throw new BizErrorException(INVALID_PAY_PREPARE_STATUS_MESSAGE);
        }
    }

    /**
     * 标记订单进入预支付准备中。
     */
    @Override
    @Transactional(readOnly = false)
    public void markPayPreparing(MallOrder order, String payChannel) {
        order.setPayType(payChannel);
        updateOrder(order);
    }

    /** 商城子单支付成功确认（领域层）：待支付 → 已支付；自提单生成 pickupVerifyCode；幂等直接返回。 */
    @Override
    @Transactional(readOnly = false)
    public void confirmPaySuccess(MallConfirmPayDTO dto) {
        MallOrder entity = get(dto.getOrderId());
        if (entity == null) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        // 幂等：重复回调不重复处理
        if (MallOrderStatusEnum.PAID.getCode().equals(entity.getStatus())) {
            return;
        }
        if (!MallOrderStatusEnum.PENDING_PAY.getCode().equals(entity.getStatus())) {
            throw new BizErrorException(INVALID_PAY_CONFIRM_STATUS_MESSAGE);
        }
        // 核心状态流转：待支付 → 已支付
        entity.setStatus(MallOrderStatusEnum.PAID.getCode());
        entity.setPayTime(new Date());
        entity.setPayType(MallPayTypeEnum.WECHAT.getCode());
        // 自提单首次支付成功生成核销码
        if (MallDeliveryTypeEnum.PICKUP.getCode().equals(StrUtil.blankToDefault(entity.getDeliveryType(), ""))
                && StrUtil.isBlank(entity.getPickupVerifyCode())) {
            entity.setPickupVerifyCode(generateUniquePickupVerifyCode());
        }
        updateOrder(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void confirmZeroAmountPay(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        MallOrder entity = get(orderId.trim());
        if (entity == null) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        if (MallOrderStatusEnum.PAID.getCode().equals(entity.getStatus())) {
            return;
        }
        if (!MallOrderStatusEnum.PENDING_PAY.getCode().equals(entity.getStatus())) {
            throw new BizErrorException(INVALID_PAY_CONFIRM_STATUS_MESSAGE);
        }
        long amountFen = calcAmountFen(resolvePayAmount(entity));
        if (amountFen > 0) {
            throw new BizErrorException(PAY_AMOUNT_INVALID_MESSAGE);
        }
        entity.setStatus(MallOrderStatusEnum.PAID.getCode());
        entity.setPayTime(new Date());
        entity.setPayType("NONE");
        if (MallDeliveryTypeEnum.PICKUP.getCode().equals(StrUtil.blankToDefault(entity.getDeliveryType(), ""))
                && StrUtil.isBlank(entity.getPickupVerifyCode())) {
            entity.setPickupVerifyCode(generateUniquePickupVerifyCode());
        }
        updateOrder(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public MallOrder shipDeliveryOrder(String orderId, String logisticsCompany, String logisticsNo, Date shipTime) {
        if (StrUtil.isBlank(orderId)) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        MallOrder order = get(orderId.trim());
        if (order == null) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        if (!MallDeliveryTypeEnum.DELIVERY.getCode()
                .equals(StrUtil.blankToDefault(order.getDeliveryType(), MallDeliveryTypeEnum.DELIVERY.getCode()))) {
            throw new BizErrorException(ORDER_NOT_DELIVERY_MESSAGE);
        }
        Integer status = order.getStatus();
        if (!MallOrderStatusEnum.PAID.getCode().equals(status)
                && !MallOrderStatusEnum.TO_FULFILL.getCode().equals(status)) {
            throw new BizErrorException(INVALID_SHIP_STATUS_MESSAGE);
        }
        if (StrUtil.isBlank(logisticsCompany)) {
            throw new BizErrorException(LOGISTICS_COMPANY_REQUIRED_MESSAGE);
        }
        if (StrUtil.isBlank(logisticsNo)) {
            throw new BizErrorException(LOGISTICS_NO_REQUIRED_MESSAGE);
        }
        order.setLogisticsCompany(logisticsCompany.trim());
        order.setLogisticsNo(logisticsNo.trim());
        order.setShipTime(shipTime != null ? shipTime : new Date());
        order.setStatus(MallOrderStatusEnum.FULFILLING.getCode());
        updateOrder(order);
        return order;
    }

    @Override
    @Transactional(readOnly = false)
    public MallOrder cancelPendingMobileOrder(String orderId, String memberId) {
        if (StrUtil.isBlank(memberId)) {
            throw new BizErrorException(LOGIN_REQUIRED_MESSAGE);
        }
        if (StrUtil.isBlank(orderId)) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        MallOrder entity = get(orderId.trim());
        if (entity == null) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        if (!memberId.equals(entity.getMemberId())) {
            throw new BizErrorException(ORDER_MEMBER_MISMATCH_MESSAGE);
        }
        if (!MallOrderStatusEnum.PENDING_PAY.getCode().equals(entity.getStatus())) {
            throw new BizErrorException(INVALID_CANCEL_STATUS_MESSAGE);
        }
        List<MallOrderItem> items = mallOrderItemService.findByOrderIds(List.of(entity.getId()));
        if (items != null) {
            for (MallOrderItem it : items) {
                if (it == null || StrUtil.isBlank(it.getSkuId()) || it.getQuantity() == null || it.getQuantity() < 1) {
                    continue;
                }
                mallSkuService.increaseStock(it.getSkuId(), it.getQuantity());
            }
        }
        entity.setStatus(MallOrderStatusEnum.CANCELED.getCode());
        updateOrder(entity);
        return entity;
    }

    @Override
    @Transactional(readOnly = false)
    public MallOrder cancelPendingOrderBySystem(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        MallOrder entity = get(orderId.trim());
        if (entity == null) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        if (!MallOrderStatusEnum.PENDING_PAY.getCode().equals(entity.getStatus())) {
            throw new BizErrorException(INVALID_CANCEL_STATUS_MESSAGE);
        }
        List<MallOrderItem> items = mallOrderItemService.findByOrderIds(List.of(entity.getId()));
        if (items != null) {
            for (MallOrderItem it : items) {
                if (it == null || StrUtil.isBlank(it.getSkuId()) || it.getQuantity() == null || it.getQuantity() < 1) {
                    continue;
                }
                mallSkuService.increaseStock(it.getSkuId(), it.getQuantity());
            }
        }
        entity.setStatus(MallOrderStatusEnum.CANCELED.getCode());
        updateOrder(entity);
        return entity;
    }

    @Override
    public List<MallOrder> listExpiredPendingPayOrders(Integer status, Date createBefore, int limit) {
        if (status == null || createBefore == null || limit < 1) {
            return Collections.emptyList();
        }
        return getBaseMapper().listExpiredPendingPayOrders(status, createBefore, limit);
    }

    @Override
    public List<MallOrder> listByPayBatchId(String payBatchId) {
        if (StrUtil.isBlank(payBatchId)) {
            return Collections.emptyList();
        }
        MallOrderQueryVO query = new MallOrderQueryVO();
        query.setPayBatchId(payBatchId.trim());
        return findList(query);
    }

    @Override
    @Transactional(readOnly = false)
    public MallOrder verifyPickupByVerifyCode(String verifyCode, String verifyBy, String verifyByName) {
        if (StrUtil.isBlank(verifyCode)) {
            throw new BizErrorException("核销码不能为空");
        }
        String trimmed = verifyCode.trim();
        MallOrder order = getBaseMapper().selectByPickupVerifyCode(trimmed);
        if (order == null) {
            throw new BizErrorException(INVALID_PICKUP_VERIFY_MESSAGE);
        }
        if (!MallDeliveryTypeEnum.PICKUP.getCode().equals(StrUtil.blankToDefault(order.getDeliveryType(), ""))) {
            throw new BizErrorException(PICKUP_VERIFY_NOT_PICKUP_MESSAGE);
        }
        if (!MallOrderStatusEnum.PAID.getCode().equals(order.getStatus())) {
            throw new BizErrorException(INVALID_PICKUP_VERIFY_MESSAGE);
        }
        if (order.getPickupVerifyTime() != null) {
            throw new BizErrorException(PICKUP_ALREADY_VERIFIED_MESSAGE);
        }
        if (!trimmed.equals(StrUtil.blankToDefault(order.getPickupVerifyCode(), ""))) {
            throw new BizErrorException(INVALID_PICKUP_VERIFY_MESSAGE);
        }
        Date now = new Date();
        order.setPickupVerifyTime(now);
        order.setPickupVerifyBy(StrUtil.blankToDefault(verifyBy, null));
        order.setPickupVerifyByName(StrUtil.blankToDefault(verifyByName, null));
        order.setConfirmTime(now);
        order.setStatus(MallOrderStatusEnum.FULFILLED.getCode());
        updateOrder(order);
        return order;
    }

    @Override
    @Transactional(readOnly = false)
    public MallOrder confirmReceiveByMember(String orderId, String memberId) {
        if (StrUtil.isBlank(memberId)) {
            throw new BizErrorException(LOGIN_REQUIRED_MESSAGE);
        }
        if (StrUtil.isBlank(orderId)) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        MallOrder order = get(orderId.trim());
        if (order == null) {
            throw new BizErrorException(ORDER_NOT_FOUND_MESSAGE);
        }
        if (!memberId.equals(order.getMemberId())) {
            throw new BizErrorException(ORDER_MEMBER_MISMATCH_MESSAGE);
        }
        if (!MallDeliveryTypeEnum.DELIVERY.getCode()
                .equals(StrUtil.blankToDefault(order.getDeliveryType(), MallDeliveryTypeEnum.DELIVERY.getCode()))) {
            throw new BizErrorException("仅快递订单支持确认收货");
        }
        if (!MallOrderStatusEnum.FULFILLING.getCode().equals(order.getStatus())) {
            throw new BizErrorException("当前状态不可确认收货");
        }
        Date now = new Date();
        order.setConfirmTime(now);
        order.setStatus(MallOrderStatusEnum.FULFILLED.getCode());
        updateOrder(order);
        return order;
    }

    private String generateUniquePickupVerifyCode() {
        for (int i = 0; i < PICKUP_VERIFY_CODE_RETRY; i++) {
            String candidate = RandomUtil.randomStringUpper(PICKUP_VERIFY_CODE_LEN);
            MallOrder exists = getBaseMapper().selectByPickupVerifyCode(candidate);
            if (exists == null) {
                return candidate;
            }
        }
        throw new BizErrorException("自提核销码生成失败，请重试");
    }

    private static BigDecimal resolvePayAmount(MallOrder order) {
        if (order.getPayAmount() != null && order.getPayAmount().compareTo(BigDecimal.ZERO) > 0) {
            return order.getPayAmount();
        }
        return order.getTotalAmount() == null ? BigDecimal.ZERO : order.getTotalAmount();
    }

    private static long calcAmountFen(BigDecimal amount) {
        BigDecimal total = amount == null ? BigDecimal.ZERO : amount;
        return total.multiply(BigDecimal.valueOf(100)).longValue();
    }

    /**
     * 锁定商品所属商户租户，避免 C 端 {@code preInsert} 将会员上下文（如 {@code user_data}）写入订单。
     */
    private static void sealMerchantTenant(MallOrder order) {
        if (order == null) {
            return;
        }
        if (StrUtil.isNotBlank(order.getTenantCode()) || StrUtil.isNotBlank(order.getTenantId())) {
            order.closeTenantCodeAutoSet();
        }
    }

    private static void sealMerchantTenant(MallOrderItem item) {
        if (item == null) {
            return;
        }
        if (StrUtil.isNotBlank(item.getTenantCode()) || StrUtil.isNotBlank(item.getTenantId())) {
            item.closeTenantCodeAutoSet();
        }
    }
}
