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

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.trade.dto.TradeOrderCreateDTO;
import com.jeesharp.business.modules.trade.dto.TradeOrderLineCreateDTO;
import com.jeesharp.business.modules.trade.entity.TradeOrder;
import com.jeesharp.business.modules.trade.entity.TradeOrderItem;
import com.jeesharp.business.modules.trade.enums.TradeBizTypeEnum;
import com.jeesharp.business.modules.trade.enums.TradeOrderStatusEnum;
import com.jeesharp.business.modules.trade.mapper.TradeOrderItemMapper;
import com.jeesharp.business.modules.trade.mapper.TradeOrderMapper;
import com.jeesharp.business.modules.trade.service.TradeOrderItemService;
import com.jeesharp.business.modules.trade.service.TradeOrderService;
import com.jeesharp.business.modules.trade.vo.TradeOrderQueryVO;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.mybatis.core.service.CrudService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 统一交易订单领域服务：{@code biz_trade_order} 与 {@code biz_trade_order_item} 的持久化与状态机。
 *
 * <p>核心状态流转：PENDING_PAY → PAID → FULFILLING → FULFILLED；预约核销 PAID → CLOSED；取消/退款见各 mark 方法。</p>
 * <p>各垂直模块通过 Facade 调用；支付金额、商品明细在创建时由垂直业务传入。</p>
 *
 * @author jeesharp
 * @since 2026-05-08
 */
@Slf4j
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class TradeOrderServiceImpl extends CrudService<TradeOrderMapper, TradeOrder, TradeOrderQueryVO>
    implements TradeOrderService {

    private static final String ORDER_NO_PREFIX = "UTO";
    private static final String MSG_UNKNOWN_BIZ = "不支持的业务类型";
    private static final String MSG_ORDER_CREATE_FAILED = "创建统一订单失败，请稍后重试";
    private static final String MSG_ORDER_NOT_FOUND = "订单不存在";
    private static final String MSG_STATUS_NOT_PENDING = "订单状态不允许该操作";
    private static final String MSG_STATUS_CANNOT_CANCEL = "订单状态不可取消";
    private static final String MSG_STATUS_CANNOT_CLOSE = "订单状态不可关闭";
    private static final String MSG_STATUS_CANNOT_FULFILL = "订单状态不可推进履约";
    private static final String MSG_STATUS_CANNOT_COMPLETE = "订单状态不可完成";

    private final TradeOrderItemService tradeOrderItemService;
    private final TradeOrderItemMapper tradeOrderItemMapper;

    @Override
    @Transactional(readOnly = false)
    public TradeOrder createPendingPayOrder(TradeOrderCreateDTO dto) {
        validateCreate(dto);
        TradeOrder order = new TradeOrder();
        order.setId(IdUtil.getSnowflakeNextIdStr());
        order.setOrderNo(resolveOrderNo(order.getId(), dto));
        order.setBizType(dto.getBizType());
        order.setMemberId(dto.getMemberId());
        order.setTitle(dto.getTitle());
        order.setTotalAmount(dto.getTotalAmount());
        order.setDiscountAmount(dto.getDiscountAmount() == null ? BigDecimal.ZERO : dto.getDiscountAmount());
        order.setPayAmount(dto.getPayAmount());
        order.setCurrency(StrUtil.blankToDefault(dto.getCurrency(), "CNY"));
        order.setStatus(TradeOrderStatusEnum.PENDING_PAY.getValue());
        order.setExpireTime(dto.getExpireTime());
        order.setVerticalOrderId(dto.getVerticalOrderId());
        order.setTenantCode(dto.getTenantCode());
        order.setTenantId(dto.getTenantId());
        if (StrUtil.isNotBlank(dto.getTenantCode()) || StrUtil.isNotBlank(dto.getTenantId())) {
            order.closeTenantCodeAutoSet();
        }
        order.setExtJson(dto.getExtJson());
        persistNewTradeOrder(order);

        if (dto.getLines() != null) {
            for (TradeOrderLineCreateDTO lineDto : dto.getLines()) {
                TradeOrderItem item = new TradeOrderItem();
                item.setId(IdUtil.getSnowflakeNextIdStr());
                item.setOrderId(order.getId());
                item.setLineNo(lineDto.getLineNo());
                item.setItemTitle(lineDto.getItemTitle());
                item.setSkuRef(lineDto.getSkuRef());
                item.setResourceRef(lineDto.getResourceRef());
                item.setQuantity(lineDto.getQuantity());
                item.setUnitPrice(lineDto.getUnitPrice());
                item.setLineAmount(lineDto.getLineAmount());
                item.setSnapshotJson(lineDto.getSnapshotJson());
                item.setTenantCode(order.getTenantCode());
                item.setTenantId(order.getTenantId());
                persistNewTradeOrderItem(item);
            }
        }
        return order;
    }

    @Override
    @Transactional(readOnly = false)
    public void markPaid(String orderId, String payOrderId, String payChannel) {
        TradeOrder order = requirePendingOrder(orderId);
        order.setStatus(TradeOrderStatusEnum.PAID.getValue());
        order.setPayOrderId(payOrderId);
        order.setPayChannel(payChannel);
        order.setPayTime(LocalDateTime.now());
        order.setUpdateDate(LocalDateTime.now());
        // 禁止 save(order)：CrudService.save → MP ServiceImpl.save 固定走 insert，有 id 也会再插一行
        persistExistingTradeOrder(order);
    }

    @Override
    @Transactional(readOnly = false)
    public void cancelPending(String orderId) {
        TradeOrder order = requirePendingOrder(orderId);
        order.setStatus(TradeOrderStatusEnum.CANCELLED.getValue());
        order.setUpdateDate(LocalDateTime.now());
        persistExistingTradeOrder(order);
    }

    @Override
    @Transactional(readOnly = false)
    public void markCancelled(String orderId) {
        TradeOrder order = requireOrder(orderId);
        Integer status = order.getStatus();
        if (TradeOrderStatusEnum.CANCELLED.getValue().equals(status)
                || TradeOrderStatusEnum.CLOSED.getValue().equals(status)
                || TradeOrderStatusEnum.REFUNDED.getValue().equals(status)) {
            return;
        }
        if (!TradeOrderStatusEnum.PENDING_PAY.getValue().equals(status)
                && !TradeOrderStatusEnum.PAID.getValue().equals(status)) {
            throw new BizErrorException(MSG_STATUS_CANNOT_CANCEL);
        }
        order.setStatus(TradeOrderStatusEnum.CANCELLED.getValue());
        order.setUpdateDate(LocalDateTime.now());
        persistExistingTradeOrder(order);
    }

    @Override
    @Transactional(readOnly = false)
    public void markClosed(String orderId) {
        TradeOrder order = requireOrder(orderId);
        if (TradeOrderStatusEnum.CLOSED.getValue().equals(order.getStatus())
                || TradeOrderStatusEnum.FULFILLED.getValue().equals(order.getStatus())) {
            return;
        }
        if (!TradeOrderStatusEnum.PAID.getValue().equals(order.getStatus())) {
            throw new BizErrorException(MSG_STATUS_CANNOT_CLOSE);
        }
        order.setStatus(TradeOrderStatusEnum.CLOSED.getValue());
        order.setUpdateDate(LocalDateTime.now());
        persistExistingTradeOrder(order);
    }

    @Override
    @Transactional(readOnly = false)
    public void markFulfilling(String orderId) {
        TradeOrder order = requireOrder(orderId);
        Integer status = order.getStatus();
        if (TradeOrderStatusEnum.FULFILLING.getValue().equals(status)
                || TradeOrderStatusEnum.FULFILLED.getValue().equals(status)
                || TradeOrderStatusEnum.CLOSED.getValue().equals(status)) {
            return;
        }
        if (!TradeOrderStatusEnum.PAID.getValue().equals(status)) {
            throw new BizErrorException(MSG_STATUS_CANNOT_FULFILL);
        }
        order.setStatus(TradeOrderStatusEnum.FULFILLING.getValue());
        order.setUpdateDate(LocalDateTime.now());
        persistExistingTradeOrder(order);
    }

    @Override
    @Transactional(readOnly = false)
    public void markFulfilled(String orderId) {
        TradeOrder order = requireOrder(orderId);
        Integer status = order.getStatus();
        if (TradeOrderStatusEnum.FULFILLED.getValue().equals(status)
                || TradeOrderStatusEnum.CLOSED.getValue().equals(status)) {
            return;
        }
        if (!TradeOrderStatusEnum.PAID.getValue().equals(status)
                && !TradeOrderStatusEnum.FULFILLING.getValue().equals(status)) {
            throw new BizErrorException(MSG_STATUS_CANNOT_COMPLETE);
        }
        order.setStatus(TradeOrderStatusEnum.FULFILLED.getValue());
        order.setUpdateDate(LocalDateTime.now());
        persistExistingTradeOrder(order);
    }

    private TradeOrder requireOrder(String orderId) {
        TradeOrder order = get(orderId);
        if (order == null) {
            throw new BizErrorException(MSG_ORDER_NOT_FOUND);
        }
        return order;
    }

    private TradeOrder requirePendingOrder(String orderId) {
        TradeOrder order = get(orderId);
        if (order == null) {
            throw new BizErrorException(MSG_ORDER_NOT_FOUND);
        }
        if (!TradeOrderStatusEnum.PENDING_PAY.getValue().equals(order.getStatus())) {
            throw new BizErrorException(MSG_STATUS_NOT_PENDING);
        }
        return order;
    }

    /**
     * 展示订单号：优先与垂直业务单绑定（便于双写幂等、避免与历史 UTO+主键 形态冲突）；否则沿用 UTO+统一单主键。
     */
    private static String resolveOrderNo(String tradeOrderId, TradeOrderCreateDTO dto) {
        if (StrUtil.isNotBlank(dto.getVerticalOrderId()) && StrUtil.isNotBlank(dto.getBizType())) {
            String candidate = ORDER_NO_PREFIX + dto.getBizType() + "-" + dto.getVerticalOrderId();
            if (candidate.length() <= 64) {
                return candidate;
            }
        }
        return ORDER_NO_PREFIX + tradeOrderId;
    }

    /**
     * 新建统一单：显式 insert，避免 preset 主键走 saveOrUpdate 未落库（美食/酒店等 M5 双写依赖）。
     */
    private void persistNewTradeOrder(TradeOrder order) {
        order.preInsert();
        if (StrUtil.isBlank(order.getDelFlag())) {
            order.setDelFlag("0");
        }
        if (order.getCreateDate() == null) {
            order.setCreateDate(LocalDateTime.now());
        }
        if (order.getUpdateDate() == null) {
            order.setUpdateDate(LocalDateTime.now());
        }
        mapper.insert(order);
        if (mapper.selectById(order.getId()) == null) {
            log.error("Trade order insert verification failed, id={}, bizType={}, verticalOrderId={}",
                    order.getId(), order.getBizType(), order.getVerticalOrderId());
            throw new BizErrorException(MSG_ORDER_CREATE_FAILED);
        }
    }

    /**
     * 更新已存在统一单：显式 {@code mapper.update}。
     * <p>{@link CrudService#save} 在本框架下委托 MP {@code ServiceImpl.save}，语义为<b>新增 insert</b>，
     * 不会因 entity 已有主键而改 UPDATE；对已落库订单调用 save 会再插一行并触发 {@code uk_order_no_tenant}。</p>
     */
    private void persistExistingTradeOrder(TradeOrder order) {
        order.preUpdate();
        if (order.getUpdateDate() == null) {
            order.setUpdateDate(LocalDateTime.now());
        }
        mapper.update(order);
    }

    private void persistNewTradeOrderItem(TradeOrderItem item) {
        item.preInsert();
        if (StrUtil.isBlank(item.getDelFlag())) {
            item.setDelFlag("0");
        }
        if (item.getCreateDate() == null) {
            item.setCreateDate(LocalDateTime.now());
        }
        if (item.getUpdateDate() == null) {
            item.setUpdateDate(LocalDateTime.now());
        }
        tradeOrderItemMapper.insert(item);
    }

    private static void validateCreate(TradeOrderCreateDTO dto) {
        if (TradeBizTypeEnum.fromValue(dto.getBizType()) == null) {
            throw new BizErrorException(MSG_UNKNOWN_BIZ);
        }
        if (dto.getTotalAmount() == null || dto.getTotalAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw new BizErrorException("订单总额非法");
        }
        if (dto.getPayAmount() == null || dto.getPayAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw new BizErrorException("实付金额非法");
        }
        if (dto.getDiscountAmount() != null && dto.getDiscountAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw new BizErrorException("优惠金额非法");
        }
    }
}
