package com.ruoyi.web.modules.order.service.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.web.modules.account.constant.MemberConstants;
import com.ruoyi.web.modules.account.domain.BizMember;
import com.ruoyi.web.modules.account.domain.BizMemberAddress;
import com.ruoyi.web.modules.account.facade.IMemberFacade;
import com.ruoyi.web.modules.account.mapper.BizMemberAddressMapper;
import com.ruoyi.web.modules.cart.constant.CartConstants;
import com.ruoyi.web.modules.cart.dto.CartItemRowDTO;
import com.ruoyi.web.modules.cart.mapper.CartAppMapper;
import com.ruoyi.web.modules.goods.domain.BizGoods;
import com.ruoyi.web.modules.goods.domain.BizGoodsAttr;
import com.ruoyi.web.modules.goods.domain.BizGoodsFreight;
import com.ruoyi.web.modules.goods.facade.IGoodsPurchaseFacade;
import com.ruoyi.web.modules.goods.mapper.BizGoodsAttrMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsFreightMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsServiceSnapshotMapper;
import com.ruoyi.web.modules.goods.vo.GoodsPurchaseVO;
import com.ruoyi.web.modules.goods.vo.GoodsServiceSnapshotVO;
import com.ruoyi.web.modules.order.constant.CheckoutConstants;
import com.ruoyi.web.modules.order.constant.OrderConstants;
import com.ruoyi.web.modules.order.domain.BizOrder;
import com.ruoyi.web.modules.order.domain.BizOrderItem;
import com.ruoyi.web.modules.order.dto.CheckoutLineDTO;
import com.ruoyi.web.modules.order.dto.CheckoutPreviewDTO;
import com.ruoyi.web.modules.order.dto.CheckoutSubmitDTO;
import com.ruoyi.web.modules.order.mapper.BizOrderItemMapper;
import com.ruoyi.web.modules.order.mapper.BizOrderMapper;
import com.ruoyi.web.modules.order.service.ICheckoutAppService;
import com.ruoyi.web.modules.order.support.CheckoutLineModel;
import com.ruoyi.web.modules.order.support.CheckoutSupport;
import com.ruoyi.web.modules.order.vo.CheckoutAddressVO;
import com.ruoyi.web.modules.order.vo.CheckoutPreviewItemVO;
import com.ruoyi.web.modules.order.vo.CheckoutPreviewVO;
import com.ruoyi.web.modules.order.vo.CheckoutShopVO;
import com.ruoyi.web.modules.order.vo.CheckoutSubmitResultVO;
import com.ruoyi.web.modules.store.domain.BizShop;
import com.ruoyi.web.modules.store.mapper.BizShopMapper;
import com.ruoyi.web.modules.template.constant.AttrTemplateConstants;

/**
 * C 端确认订单 Service 实现
 */
@Service
public class CheckoutAppServiceImpl implements ICheckoutAppService
{
    @Autowired
    private IMemberFacade memberFacade;

    @Autowired
    private IGoodsPurchaseFacade purchaseFacade;

    @Autowired
    private CartAppMapper cartAppMapper;

    @Autowired
    private BizGoodsMapper goodsMapper;

    @Autowired
    private BizGoodsAttrMapper goodsAttrMapper;

    @Autowired
    private BizGoodsFreightMapper goodsFreightMapper;

    @Autowired
    private BizGoodsServiceSnapshotMapper snapshotMapper;

    @Autowired
    private BizMemberAddressMapper addressMapper;

    @Autowired
    private BizShopMapper shopMapper;

    @Autowired
    private BizOrderMapper orderMapper;

    @Autowired
    private BizOrderItemMapper orderItemMapper;

    @Autowired
    private CheckoutSupport checkoutSupport;

    @Autowired
    private ISysConfigService configService;

    @Override
    public CheckoutPreviewVO preview(Long memberId, CheckoutPreviewDTO dto)
    {
        assertMemberEnabled(memberId);
        validateSource(dto != null ? dto.getSource() : null);
        assertBuyNowOnly(dto);
        List<CheckoutLineModel> lines = resolveLines(memberId, dto, false);
        checkoutSupport.assertSameShop(lines);
        validatePurchaseAndStock(lines);
        BizMemberAddress address = resolveAddress(memberId, dto != null ? dto.getAddressId() : null, false);
        return buildPreview(memberId, lines, address);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CheckoutSubmitResultVO submit(Long memberId, CheckoutSubmitDTO dto)
    {
        assertMemberEnabled(memberId);
        validateSource(dto != null ? dto.getSource() : null);
        validatePayType(dto != null ? dto.getPayType() : null);
        CheckoutPreviewDTO previewDto = toPreviewDto(dto);
        List<CheckoutLineModel> lines = resolveLines(memberId, previewDto, true);
        checkoutSupport.assertSameShop(lines);
        validatePurchaseAndStock(lines);
        validateRemarks(lines);
        BizMemberAddress address = resolveAddress(memberId, dto.getAddressId(), true);
        CheckoutPreviewVO preview = buildPreview(memberId, lines, address);
        Date now = new Date();
        BizMember member = memberFacade.getMember(memberId);
        BizShop shop = shopMapper.selectById(lines.get(0).getShopId());
        if (shop == null)
        {
            throw new ServiceException(CartConstants.MSG_INVALID_SHOP_DELETED);
        }
        BizOrder order = new BizOrder();
        order.setOrderNo(checkoutSupport.nextOrderNo(now));
        order.setMemberId(memberId);
        order.setShopId(shop.getShopId());
        order.setMerchantId(shop.getMerchantId());
        order.setOrderStatus(OrderConstants.STATUS_PENDING_PAY);
        order.setPayStatus(OrderConstants.PAY_UNPAID);
        order.setGoodsAmount(preview.getGoodsAmount());
        order.setFreightAmount(preview.getFreightAmount());
        order.setPayAmount(preview.getPayAmount());
        order.setConsigneeName(address.getConsigneeName());
        order.setConsigneeMobile(address.getMobile());
        order.setConsigneeAddress(checkoutSupport.buildConsigneeAddress(address));
        order.setMemberNickName(member != null ? member.getNickName() : "");
        order.setShopName(shop.getShopName());
        order.setCreateTime(now);
        order.setPayExpireTime(calcPayExpireTime(now));
        orderMapper.insert(order);
        List<BizOrderItem> items = new ArrayList<>();
        int sortNo = 0;
        for (CheckoutLineModel line : lines)
        {
            BizOrderItem item = new BizOrderItem();
            item.setOrderId(order.getOrderId());
            item.setGoodsId(line.getGoodsId());
            item.setGoodsName(line.getGoodsName());
            item.setGoodsSpec(line.getSpecText());
            item.setGoodsImage(line.getMainPic());
            item.setUnitPrice(line.getSalePrice());
            item.setQuantity(line.getQuantity());
            item.setLineAmount(line.getSubtotal());
            item.setSortNo(sortNo++);
            item.setBuyerRemark(line.getBuyerRemark() != null ? line.getBuyerRemark() : "");
            item.setServiceDesc(checkoutSupport.buildServiceDesc(snapshotMapper.selectVisibleByGoodsId(line.getGoodsId())));
            item.setCartItemId(line.getCartItemId());
            item.setSpecKey(normalizeSpecKey(line.getSpecKey()));
            items.add(item);
        }
        orderItemMapper.batchInsert(items);
        return new CheckoutSubmitResultVO(order.getOrderId(), order.getOrderNo(), order.getPayAmount(),
                order.getPayExpireTime());
    }

    private CheckoutPreviewVO buildPreview(Long memberId, List<CheckoutLineModel> lines, BizMemberAddress address)
    {
        Long shopId = lines.get(0).getShopId();
        BizShop shop = shopMapper.selectById(shopId);
        CheckoutPreviewVO vo = new CheckoutPreviewVO();
        CheckoutShopVO shopVo = new CheckoutShopVO();
        if (shop != null)
        {
            shopVo.setShopId(shop.getShopId());
            shopVo.setShopName(shop.getShopName());
            shopVo.setShopAvatar(shop.getShopAvatar());
            shopVo.setShopStatus(shop.getShopStatus());
        }
        vo.setShop(shopVo);
        vo.setAddress(toAddressVo(address));
        List<CheckoutPreviewItemVO> itemVos = new ArrayList<>();
        for (CheckoutLineModel line : lines)
        {
            CheckoutPreviewItemVO itemVo = new CheckoutPreviewItemVO();
            itemVo.setCartItemId(line.getCartItemId());
            itemVo.setGoodsId(line.getGoodsId());
            itemVo.setGoodsName(line.getGoodsName());
            itemVo.setMainPic(line.getMainPic());
            itemVo.setSpecText(line.getSpecText());
            itemVo.setServiceDesc(checkoutSupport.buildServiceDesc(snapshotMapper.selectVisibleByGoodsId(line.getGoodsId())));
            itemVo.setSalePrice(line.getSalePrice());
            itemVo.setQuantity(line.getQuantity());
            itemVo.setSubtotal(line.getSubtotal());
            itemVo.setMaxStock(line.getMaxStock());
            BizGoodsFreight freight = goodsFreightMapper.selectByGoodsId(line.getGoodsId());
            if (freight != null)
            {
                itemVo.setFreightType(freight.getFreightType());
                itemVo.setFreightDesc(checkoutSupport.buildLineFreightDesc(freight, shopId));
            }
            itemVos.add(itemVo);
        }
        vo.setItems(itemVos);
        BigDecimal goodsAmount = checkoutSupport.sumGoodsAmount(lines);
        vo.setGoodsAmount(goodsAmount);
        Integer provinceId = checkoutSupport.resolveProvinceId(address);
        BigDecimal freightAmount = checkoutSupport.calcFreight(shopId, lines, provinceId, goodsAmount);
        vo.setFreightAmount(freightAmount);
        vo.setFreightDesc(checkoutSupport.buildFreightDesc(shopId, lines, freightAmount, goodsAmount));
        vo.setPayAmount(goodsAmount.add(freightAmount).setScale(2, RoundingMode.HALF_UP));
        vo.setPayType(CheckoutConstants.PAY_TYPE_WECHAT);
        return vo;
    }

    private List<CheckoutLineModel> resolveLines(Long memberId, CheckoutPreviewDTO dto, boolean submit)
    {
        if (CheckoutConstants.SOURCE_CART.equals(dto.getSource()))
        {
            return resolveCartLines(memberId, dto);
        }
        return resolveBuyNowLines(dto, submit);
    }

    private List<CheckoutLineModel> resolveCartLines(Long memberId, CheckoutPreviewDTO dto)
    {
        if (dto.getCartItemIds() == null || dto.getCartItemIds().isEmpty())
        {
            throw new ServiceException(CheckoutConstants.MSG_CART_ITEMS_REQUIRED);
        }
        List<CartItemRowDTO> rows = cartAppMapper.selectItemRowsByIdsAndMember(memberId, dto.getCartItemIds());
        if (rows == null || rows.size() != dto.getCartItemIds().size())
        {
            throw new ServiceException(CartConstants.MSG_ITEM_NOT_FOUND);
        }
        Map<Long, CheckoutLineDTO> overrideMap = buildOverrideMap(dto.getItems());
        List<CheckoutLineModel> lines = new ArrayList<>();
        for (CartItemRowDTO row : rows)
        {
            CheckoutLineDTO override = overrideMap.get(row.getCartItemId());
            int quantity = row.getQuantity() != null ? row.getQuantity() : 1;
            if (override != null && override.getQuantity() != null)
            {
                quantity = override.getQuantity();
            }
            CheckoutLineModel line = buildLineFromCartRow(row, quantity, override);
            lines.add(line);
        }
        return lines;
    }

    private List<CheckoutLineModel> resolveBuyNowLines(CheckoutPreviewDTO dto, boolean submit)
    {
        if (dto.getItems() == null || dto.getItems().isEmpty())
        {
            throw new ServiceException(CheckoutConstants.MSG_BUY_NOW_ITEMS_REQUIRED);
        }
        if (dto.getItems().size() != 1)
        {
            throw new ServiceException(CheckoutConstants.MSG_BUY_NOW_SINGLE_ONLY);
        }
        CheckoutLineDTO item = dto.getItems().get(0);
        if (item.getGoodsId() == null)
        {
            throw new ServiceException(CartConstants.MSG_GOODS_NOT_FOUND);
        }
        int quantity = item.getQuantity() != null ? item.getQuantity() : 1;
        if (quantity < 1)
        {
            throw new ServiceException(CheckoutConstants.MSG_QUANTITY_MIN);
        }
        BizGoods goods = goodsMapper.selectById(item.getGoodsId());
        if (goods == null)
        {
            throw new ServiceException(CartConstants.MSG_GOODS_NOT_FOUND);
        }
        CheckoutLineModel line = new CheckoutLineModel();
        line.setCartItemId(null);
        line.setGoodsId(goods.getGoodsId());
        line.setShopId(goods.getShopId());
        line.setSpecKey(normalizeSpecKey(item.getSpecKey()));
        line.setSpecText(resolveSpecText(item.getSpecText(), goods.getGoodsId()));
        line.setQuantity(quantity);
        line.setBuyerRemark(submit ? trimRemark(item.getBuyerRemark()) : null);
        line.setGoodsName(goods.getGoodsName());
        line.setMainPic(goods.getMainPic());
        line.setSalePrice(goods.getSalePrice());
        line.setMaxStock(goods.getStock());
        line.setSubtotal(calcSubtotal(goods.getSalePrice(), quantity));
        return Collections.singletonList(line);
    }

    private CheckoutLineModel buildLineFromCartRow(CartItemRowDTO row, int quantity, CheckoutLineDTO override)
    {
        if (quantity < 1)
        {
            throw new ServiceException(CheckoutConstants.MSG_QUANTITY_MIN);
        }
        CheckoutLineModel line = new CheckoutLineModel();
        line.setCartItemId(row.getCartItemId());
        line.setGoodsId(row.getGoodsId());
        line.setShopId(row.getShopId());
        line.setSpecKey(normalizeSpecKey(override != null ? override.getSpecKey() : null));
        String specText = row.getSpecText();
        if (override != null && StringUtils.isNotEmpty(override.getSpecText()))
        {
            specText = override.getSpecText();
        }
        line.setSpecText(StringUtils.isNotEmpty(specText) ? specText : CartConstants.DEFAULT_SPEC_TEXT);
        line.setQuantity(quantity);
        line.setBuyerRemark(override != null ? trimRemark(override.getBuyerRemark()) : null);
        line.setGoodsName(row.getGoodsName());
        line.setMainPic(row.getMainPic());
        line.setSalePrice(row.getSalePrice());
        line.setMaxStock(row.getStock());
        line.setSubtotal(calcSubtotal(row.getSalePrice(), quantity));
        return line;
    }

    private Map<Long, CheckoutLineDTO> buildOverrideMap(List<CheckoutLineDTO> items)
    {
        Map<Long, CheckoutLineDTO> map = new HashMap<>();
        if (items == null)
        {
            return map;
        }
        for (CheckoutLineDTO item : items)
        {
            if (item != null && item.getCartItemId() != null)
            {
                map.put(item.getCartItemId(), item);
            }
        }
        return map;
    }

    private void validatePurchaseAndStock(List<CheckoutLineModel> lines)
    {
        for (CheckoutLineModel line : lines)
        {
            GoodsPurchaseVO purchase = purchaseFacade.canPurchase(line.getGoodsId());
            if (!purchase.isAllowed())
            {
                throw new ServiceException(StringUtils.isNotEmpty(purchase.getReason()) ? purchase.getReason()
                        : CartConstants.MSG_INVALID_OFF_SHELF);
            }
            if (line.getQuantity() == null || line.getQuantity() < 1)
            {
                throw new ServiceException(CheckoutConstants.MSG_QUANTITY_MIN);
            }
            if (line.getMaxStock() == null || line.getQuantity() > line.getMaxStock())
            {
                throw new ServiceException(CheckoutConstants.MSG_STOCK_INSUFFICIENT);
            }
        }
    }

    private void validateRemarks(List<CheckoutLineModel> lines)
    {
        for (CheckoutLineModel line : lines)
        {
            if (line.getBuyerRemark() != null && line.getBuyerRemark().length() > CheckoutConstants.MAX_BUYER_REMARK_LEN)
            {
                throw new ServiceException(CheckoutConstants.MSG_REMARK_TOO_LONG);
            }
        }
    }

    private BizMemberAddress resolveAddress(Long memberId, Long addressId, boolean required)
    {
        BizMemberAddress address = null;
        if (addressId != null)
        {
            address = addressMapper.selectById(addressId, memberId);
            if (address == null)
            {
                throw new ServiceException(CheckoutConstants.MSG_ADDRESS_NOT_FOUND);
            }
            return address;
        }
        List<BizMemberAddress> addresses = addressMapper.selectByMemberId(memberId);
        if (addresses == null || addresses.isEmpty())
        {
            if (required)
            {
                throw new ServiceException(CheckoutConstants.MSG_ADDRESS_REQUIRED);
            }
            return null;
        }
        for (BizMemberAddress item : addresses)
        {
            if (MemberConstants.DEFAULT_YES.equals(item.getIsDefault()))
            {
                return item;
            }
        }
        if (addresses.size() == 1)
        {
            return addresses.get(0);
        }
        if (required)
        {
            throw new ServiceException(CheckoutConstants.MSG_ADDRESS_REQUIRED);
        }
        return null;
    }

    private CheckoutAddressVO toAddressVo(BizMemberAddress address)
    {
        if (address == null)
        {
            return null;
        }
        CheckoutAddressVO vo = new CheckoutAddressVO();
        vo.setAddressId(address.getAddressId());
        vo.setConsigneeName(address.getConsigneeName());
        vo.setMobile(address.getMobile());
        vo.setRegionName(address.getRegionName());
        vo.setDetailAddress(address.getDetailAddress());
        vo.setIsDefault(address.getIsDefault());
        return vo;
    }

    private CheckoutPreviewDTO toPreviewDto(CheckoutSubmitDTO submit)
    {
        CheckoutPreviewDTO dto = new CheckoutPreviewDTO();
        dto.setSource(submit.getSource());
        dto.setAddressId(submit.getAddressId());
        dto.setCartItemIds(submit.getCartItemIds());
        dto.setItems(submit.getItems());
        return dto;
    }

    private void assertMemberEnabled(Long memberId)
    {
        if (!memberFacade.isMemberEnabled(memberId))
        {
            throw new ServiceException(CheckoutConstants.MSG_MEMBER_DISABLED, 403);
        }
    }

    private void validateSource(String source)
    {
        if (StringUtils.isEmpty(source))
        {
            throw new ServiceException(CheckoutConstants.MSG_SOURCE_REQUIRED);
        }
        if (!CheckoutConstants.SOURCE_CART.equals(source) && !CheckoutConstants.SOURCE_BUY_NOW.equals(source))
        {
            throw new ServiceException(CheckoutConstants.MSG_SOURCE_INVALID);
        }
    }

    private void assertBuyNowOnly(CheckoutPreviewDTO dto)
    {
        if (dto == null || !CheckoutConstants.SOURCE_BUY_NOW.equals(dto.getSource()))
        {
            return;
        }
        if (dto.getCartItemIds() != null && !dto.getCartItemIds().isEmpty())
        {
            throw new ServiceException(CheckoutConstants.MSG_BUY_NOW_CART_FORBIDDEN);
        }
    }

    private void validatePayType(String payType)
    {
        if (StringUtils.isEmpty(payType))
        {
            throw new ServiceException(CheckoutConstants.MSG_PAY_TYPE_REQUIRED);
        }
        if (!OrderConstants.PAY_TYPE_WECHAT.equals(payType))
        {
            throw new ServiceException(CheckoutConstants.MSG_PAY_TYPE_INVALID);
        }
    }

    private String resolveSpecText(String specText, Long goodsId)
    {
        if (StringUtils.isNotEmpty(specText))
        {
            return specText;
        }
        List<BizGoodsAttr> attrs = goodsAttrMapper.selectByGoodsId(goodsId);
        if (attrs == null || attrs.isEmpty())
        {
            return CartConstants.DEFAULT_SPEC_TEXT;
        }
        StringBuilder sb = new StringBuilder();
        for (BizGoodsAttr attr : attrs)
        {
            if (!AttrTemplateConstants.ITEM_TYPE_SPEC.equals(attr.getAttrType()))
            {
                continue;
            }
            if (sb.length() > 0)
            {
                sb.append("；");
            }
            sb.append(attr.getItemName()).append(":").append(attr.getValueText());
        }
        return sb.length() > 0 ? sb.toString() : CartConstants.DEFAULT_SPEC_TEXT;
    }

    private String normalizeSpecKey(String specKey)
    {
        return specKey != null ? specKey : CheckoutConstants.DEFAULT_SPEC_KEY;
    }

    private String trimRemark(String remark)
    {
        return remark != null ? remark.trim() : "";
    }

    private BigDecimal calcSubtotal(BigDecimal price, int quantity)
    {
        if (price == null)
        {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        return price.multiply(BigDecimal.valueOf(quantity)).setScale(2, RoundingMode.HALF_UP);
    }

    private Date calcPayExpireTime(Date now)
    {
        int minutes = OrderConstants.DEFAULT_PAY_TIMEOUT_MINUTES;
        String config = configService.selectConfigByKey(OrderConstants.CONFIG_PAY_TIMEOUT_MINUTES);
        if (StringUtils.isNotEmpty(config))
        {
            try
            {
                minutes = Integer.parseInt(config.trim());
            }
            catch (NumberFormatException ex)
            {
                minutes = OrderConstants.DEFAULT_PAY_TIMEOUT_MINUTES;
            }
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(now);
        calendar.add(Calendar.MINUTE, minutes);
        return calendar.getTime();
    }
}
