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

import com.jeesharp.business.modules.common.enums.VerticalPayStatusEnum;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.jeesharp.business.modules.food.dto.FoodBookingRefundDTO;
import com.jeesharp.business.modules.food.entity.FoodBookingOrder;
import com.jeesharp.business.modules.food.entity.FoodPackage;
import com.jeesharp.business.modules.food.entity.FoodRestaurant;
import com.jeesharp.business.modules.food.enums.FoodBookingStatusEnum;

import com.jeesharp.business.modules.food.mapper.FoodBookingOrderMapper;
import com.jeesharp.business.modules.food.service.FoodBookingOrderService;
import com.jeesharp.business.modules.food.service.FoodPackageService;
import com.jeesharp.business.modules.food.service.FoodRestaurantService;
import com.jeesharp.business.modules.food.vo.FoodBookingOrderQueryVO;
import com.jeesharp.business.modules.food.vo.FoodBookingResultVO;
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.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

/**
 * 美食团购预约单领域服务：落库、支付状态变迁与结果快照查询。
 *
 * <p>优惠券与统一单双写由 {@link com.jeesharp.business.modules.food.application.FoodBookingOrderApplicationService} 编排。</p>
 *
 * @author jeesharp
 * @since 2026-05-08
 */
@Slf4j
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class FoodBookingOrderServiceImpl extends CrudService<FoodBookingOrderMapper, FoodBookingOrder, FoodBookingOrderQueryVO>
    implements FoodBookingOrderService {

    private static final String IDS_SEPARATOR = ",";
    private static final String EMPTY_JSON_OBJECT = "{}";
    private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final String MSG_VERIFY_EMPTY = "核销码不能为空";
    private static final String MSG_VERIFY_INVALID = "核销失败，请核对核销码与订单状态";
    private static final String MSG_VERIFY_ALREADY = "该订单已核销";
    private static final String MSG_VERIFY_NOT_PENDING = "当前状态不可核销";
    private static final String MSG_ORDER_NOT_FOUND = "预订单不存在";
    private static final String MSG_REFUND_NOT_ALLOWED = "当前状态不可办理退款";
    private static final String BOOKING_ORDER_CREATE_FAILED_MESSAGE = "创建预约单失败，请稍后重试";
    private static final String BOOKING_ORDER_NOT_FOUND_MESSAGE = "预订单不存在";
    private static final String INVALID_PAY_PREPARE_STATUS_MESSAGE = "当前状态不可发起支付";
    private static final String BOOKING_ORDER_PAID_MESSAGE = "订单已支付";
    private static final String INVALID_PAY_CONFIRM_STATUS_MESSAGE = "当前状态不可确认支付";
    private static final String INVALID_CANCEL_STATUS_MESSAGE = "当前状态不可取消";
    private final FoodPackageService foodPackageService;
    private final FoodRestaurantService foodRestaurantService;

    @Override
    @Transactional(readOnly = false)
    public void createOrder(FoodBookingOrder entity) {
        if (entity.getBookStatus() == null || entity.getBookStatus().isBlank()) {
            entity.setBookStatus(FoodBookingStatusEnum.CREATED.getValue());
        }
        if (StrUtil.isNotBlank(entity.getId())) {
            persistNewBooking(entity);
            return;
        }
        save(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void updateOrder(FoodBookingOrder entity) {
        if (entity.getBookStatus() == null || entity.getBookStatus().isBlank()) {
            entity.setBookStatus(FoodBookingStatusEnum.CREATED.getValue());
        }
        update(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public FoodBookingOrder insertBooking(FoodBookingOrder order) {
        if (order == null || StrUtil.isBlank(order.getId())) {
            throw new BizErrorException(BOOKING_ORDER_CREATE_FAILED_MESSAGE);
        }
        persistNewBooking(order);
        return order;
    }

    @Override
    @Transactional(readOnly = false)
    public void verifyConsumeByVerifyCode(String verifyCode) {
        if (StrUtil.isBlank(verifyCode)) {
            throw new BizErrorException(MSG_VERIFY_EMPTY);
        }
        String trimmed = verifyCode.trim();
        FoodBookingOrder order = getBaseMapper().selectByVerifyCode(trimmed);
        if (order == null) {
            throw new BizErrorException(MSG_VERIFY_INVALID);
        }
        if (FoodBookingStatusEnum.USED.getValue().equals(order.getBookStatus())) {
            throw new BizErrorException(MSG_VERIFY_ALREADY);
        }
        if (!FoodBookingStatusEnum.PENDING_USE.getValue().equals(order.getBookStatus())) {
            throw new BizErrorException(MSG_VERIFY_NOT_PENDING);
        }
        if (StrUtil.isNotBlank(order.getPayStatus())
                && !VerticalPayStatusEnum.PAID.getValue().equals(order.getPayStatus())) {
            throw new BizErrorException(MSG_VERIFY_NOT_PENDING);
        }
        order.setBookStatus(FoodBookingStatusEnum.USED.getValue());
        order.setUsedTime(LocalDateTime.now());
        order.setResultPayloadJson(null);
        updateOrder(order);
    }

    @Override
    @Transactional(readOnly = false)
    public void markRefunded(FoodBookingRefundDTO dto) {
        if (dto == null || StrUtil.isBlank(dto.getOrderId())) {
            throw new BizErrorException(MSG_ORDER_NOT_FOUND);
        }
        FoodBookingOrder order = get(dto.getOrderId().trim());
        if (order == null) {
            throw new BizErrorException(MSG_ORDER_NOT_FOUND);
        }
        if (FoodBookingStatusEnum.REFUNDED.getValue().equals(order.getBookStatus())) {
            return;
        }
        if (!FoodBookingStatusEnum.PENDING_USE.getValue().equals(order.getBookStatus())
                && !FoodBookingStatusEnum.CREATED.getValue().equals(order.getBookStatus())) {
            throw new BizErrorException(MSG_REFUND_NOT_ALLOWED);
        }
        order.setBookStatus(FoodBookingStatusEnum.REFUNDED.getValue());
        order.setCancelTime(LocalDateTime.now());
        updateOrder(order);
    }

    @Override
    public FoodBookingOrder getByVerifyCode(String verifyCode) {
        if (StrUtil.isBlank(verifyCode)) {
            return null;
        }
        return getBaseMapper().selectByVerifyCode(verifyCode.trim());
    }

    /**
     * 按垂直单主键查询预订成功页数据：优先读 {@code result_payload_json}，失败则按门店/套餐重建。
     */
    @Override
    public FoodBookingResultVO getBookingResult(String orderId) {
        FoodBookingOrder entity = get(orderId);
        if (entity == null) {
            return null;
        }
        if (StrUtil.isNotBlank(entity.getResultPayloadJson()) && !EMPTY_JSON_OBJECT.equals(entity.getResultPayloadJson().trim())) {
            try {
                FoodBookingResultVO parsed = JSONUtil.toBean(entity.getResultPayloadJson(), FoodBookingResultVO.class);
                if (parsed != null) {
                    parsed.setOrderId(entity.getId());
                    if (StrUtil.isNotBlank(entity.getUnifiedOrderId())) {
                        parsed.setUnifiedOrderId(entity.getUnifiedOrderId());
                    }
                    if (StrUtil.isNotBlank(entity.getPayStatus())) {
                        parsed.setPayStatus(entity.getPayStatus());
                    }
                }
                return parsed;
            } catch (Exception ignored) {
                // fall through rebuild
            }
        }
        FoodRestaurant restaurant = foodRestaurantService.get(entity.getRestaurantId());
        FoodPackage pkg = foodPackageService.get(entity.getPackageId());
        if (restaurant == null || pkg == null) {
            return null;
        }
        BigDecimal unit = pkg.getPrice() == null ? BigDecimal.ZERO : pkg.getPrice();
        BigDecimal total = unit.multiply(BigDecimal.valueOf(entity.getQuantity()));
        FoodBookingResultVO vo = buildResultVo(entity.getId(), restaurant, pkg, entity.getQuantity(), total,
            entity.getVerifyCode(), entity.getBookStatus(), entity.getPayStatus());
        if (StrUtil.isNotBlank(entity.getUnifiedOrderId())) {
            vo.setUnifiedOrderId(entity.getUnifiedOrderId());
        }
        return vo;
    }

    /** 构建成功页 VO（标题、金额、核销码、状态文案等）。 */
    private static FoodBookingResultVO buildResultVo(String orderId, FoodRestaurant restaurant, FoodPackage pkg, int qty,
                                                   BigDecimal total, String verifyCode, String bookStatus,
                                                   String payStatus) {
        FoodBookingResultVO vo = new FoodBookingResultVO();
        vo.setOrderId(orderId);
        vo.setVerifyCode(verifyCode);
        vo.setStatus(bookStatus);
        vo.setPayStatus(payStatus);
        vo.setTitle(restaurant.getName() + " · " + pkg.getName() + " · " + qty + "份");
        vo.setAmountLabel(formatAmountLabel(total));
        vo.setAmount(total);
        vo.setStatusLabel(FoodBookingStatusEnum.getDescByValue(bookStatus));
        vo.setCreatedAt(LocalDateTime.now().format(DT_FMT));
        vo.setResultPayloadJson(EMPTY_JSON_OBJECT);
        return vo;
    }

    @Override
    public FoodBookingOrder findByOrderRef(String orderRef) {
        return resolveBookingOrder(orderRef);
    }

    @Override
    public void assertPayPreparable(FoodBookingOrder entity) {
        if (entity == null) {
            throw new BizErrorException(BOOKING_ORDER_NOT_FOUND_MESSAGE);
        }
        if (!FoodBookingStatusEnum.PENDING_PAY.getValue().equals(entity.getBookStatus())) {
            throw new BizErrorException(INVALID_PAY_PREPARE_STATUS_MESSAGE);
        }
        if (VerticalPayStatusEnum.PAID.getValue().equals(entity.getPayStatus())) {
            throw new BizErrorException(BOOKING_ORDER_PAID_MESSAGE);
        }
    }

    @Override
    @Transactional(readOnly = false)
    public void markPayPreparing(FoodBookingOrder entity, String payChannel, String payOrderId, LocalDateTime expireTime) {
        entity.setPayChannel(payChannel);
        entity.setPayStatus(VerticalPayStatusEnum.PREPARING.getValue());
        entity.setPayOrderId(payOrderId);
        entity.setExpireTime(expireTime);
        updateOrder(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public FoodBookingResultVO markPaySuccess(FoodBookingOrder entity, String payOrderId, String thirdPartyTradeNo) {
        if (VerticalPayStatusEnum.PAID.getValue().equals(entity.getPayStatus())) {
            return rebuildBookingResult(entity);
        }
        if (!FoodBookingStatusEnum.PENDING_PAY.getValue().equals(entity.getBookStatus())) {
            throw new BizErrorException(INVALID_PAY_CONFIRM_STATUS_MESSAGE);
        }
        entity.setPayStatus(VerticalPayStatusEnum.PAID.getValue());
        entity.setPayTime(LocalDateTime.now());
        entity.setBookStatus(FoodBookingStatusEnum.PENDING_USE.getValue());
        if (StrUtil.isNotBlank(payOrderId)) {
            entity.setPayOrderId(payOrderId);
        }
        if (StrUtil.isNotBlank(thirdPartyTradeNo)) {
            entity.setExtJson(JSONUtil.createObj()
                    .set("thirdPartyTradeNo", thirdPartyTradeNo)
                    .set("confirmedAt", LocalDateTime.now().format(DT_FMT))
                    .toString());
        }
        FoodBookingResultVO vo = rebuildBookingResult(entity);
        if (vo == null) {
            return null;
        }
        entity.setResultPayloadJson(JSONUtil.toJsonStr(vo));
        updateOrder(entity);
        return vo;
    }

    @Override
    @Transactional(readOnly = false)
    public void markCancelled(FoodBookingOrder entity, String cancelReason) {
        if (FoodBookingStatusEnum.USED.getValue().equals(entity.getBookStatus())) {
            throw new BizErrorException(INVALID_CANCEL_STATUS_MESSAGE);
        }
        if (VerticalPayStatusEnum.PAID.getValue().equals(entity.getPayStatus())
                && FoodBookingStatusEnum.PENDING_USE.getValue().equals(entity.getBookStatus())) {
            throw new BizErrorException("已支付订单请联系客服退款");
        }
        entity.setBookStatus(FoodBookingStatusEnum.CANCELLED.getValue());
        entity.setPayStatus(VerticalPayStatusEnum.CLOSED.getValue());
        entity.setCancelTime(LocalDateTime.now());
        if (StrUtil.isNotBlank(cancelReason)) {
            entity.setExtJson(JSONUtil.createObj()
                    .set("cancelReason", cancelReason)
                    .set("cancelTime", LocalDateTime.now().format(DT_FMT))
                    .toString());
        }
        updateOrder(entity);
    }

    private FoodBookingResultVO rebuildBookingResult(FoodBookingOrder entity) {
        FoodBookingResultVO parsed = getBookingResult(entity.getId());
        if (parsed != null) {
            parsed.setPayStatus(entity.getPayStatus());
            parsed.setStatus(entity.getBookStatus());
            parsed.setStatusLabel(FoodBookingStatusEnum.getDescByValue(entity.getBookStatus()));
        }
        return parsed;
    }

    private FoodBookingOrder resolveBookingOrder(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            return null;
        }
        String trimmed = orderId.trim();
        FoodBookingOrder entity = get(trimmed);
        if (entity != null) {
            return entity;
        }
        FoodBookingOrderQueryVO byTrade = new FoodBookingOrderQueryVO();
        byTrade.setTradeOrderId(trimmed);
        List<FoodBookingOrder> byTradeList = findList(byTrade);
        if (byTradeList != null && !byTradeList.isEmpty()) {
            return byTradeList.get(0);
        }
        FoodBookingOrderQueryVO byUnified = new FoodBookingOrderQueryVO();
        byUnified.setUnifiedOrderId(trimmed);
        List<FoodBookingOrder> byUnifiedList = findList(byUnified);
        if (byUnifiedList != null && !byUnifiedList.isEmpty()) {
            return byUnifiedList.get(0);
        }
        return null;
    }

    private static String formatAmountLabel(BigDecimal total) {
        if (total == null || total.compareTo(BigDecimal.ZERO) == 0) {
            return "¥0";
        }
        return "¥" + total.stripTrailingZeros().toPlainString();
    }

    /**
     * 新建美食预订单落库：显式 {@code mapper.insert}，禁止 preset 雪花 id 走 saveOrUpdate 导致未 INSERT。
     * <p>插入后立即 {@code get(id)} 校验，查不到则抛错，避免 Application 层拿到「有 id 无行」。</p>
     */
    private void persistNewBooking(FoodBookingOrder entity) {
        if (StrUtil.isNotBlank(entity.getId())) {
            entity.setIsNewRecord(true);
        }
        entity.preInsert();
        if (StrUtil.isBlank(entity.getDelFlag())) {
            entity.setDelFlag("0");
        }
        mapper.insert(entity);
        FoodBookingOrder persisted = get(entity.getId());
        if (persisted == null) {
            log.error("Food booking insert verification failed, id={}", entity.getId());
            throw new BizErrorException(BOOKING_ORDER_CREATE_FAILED_MESSAGE);
        }
    }
}
