/*
 * Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
 *
 * https://www.mall4j.com/
 *
 * 未经允许，不可做商业用途！
 *
 * 版权所有，侵权必究！
 */

package com.yami.shop.api.listener;

import com.yami.shop.bean.app.dto.ShopCartItemDto;
import com.yami.shop.bean.app.dto.ShopCartOrderDto;
import com.yami.shop.bean.app.param.OrderParam;
import com.yami.shop.bean.event.ConfirmOrderEvent;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.model.Sku;
import com.yami.shop.bean.model.UserAddr;
import com.yami.shop.bean.order.ConfirmOrderOrder;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.Arith;
import com.yami.shop.security.api.util.SecurityUtils;
import com.yami.shop.service.ProductService;
import com.yami.shop.service.SkuService;
import com.yami.shop.service.TransportManagerService;
import com.yami.shop.service.UserAddrService;
import lombok.AllArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 确认订单信息时的默认操作相关的监听器类，用于监听确认订单事件（ConfirmOrderEvent），
 * 在事件触发时执行一系列默认操作，主要包括获取相关订单信息、验证商品状态、计算订单金额（商品总价、运费等）等逻辑，
 * 确保在确认订单过程中数据的准确性和完整性。
 *
 * @author LGH
 */
@Component("defaultConfirmOrderListener")
@AllArgsConstructor
public class ConfirmOrderListener {

    // 通过Lombok生成的构造函数注入UserAddrService，用于根据用户地址ID和用户ID获取用户地址信息，
    // 在后续计算运费等操作中会用到该地址信息。
    private final UserAddrService userAddrService;

    // 注入TransportManagerService，用于根据购物车商品项和用户地址计算运费，是订单金额计算的重要组成部分。
    private final TransportManagerService transportManagerService;

    // 注入ProductService，用于根据商品ID获取商品详细信息，以验证商品状态以及参与订单金额计算等操作。
    private final ProductService productService;

    // 注入SkuService，用于根据商品规格（Sku）ID获取商品规格详细信息，同样用于验证商品状态和相关金额计算等操作。
    private final SkuService skuService;

    /**
     * 监听确认订单事件（ConfirmOrderEvent）的方法，当该事件被触发时，会执行此方法内的逻辑。
     * 通过 @EventListener 注解将此方法标记为事件监听器，使其能够响应对应的事件。
     * @Order 注解则指定了该监听器在处理确认订单事件时的执行顺序（这里按照默认顺序执行），确保相关操作按照预期的顺序进行。
     * 方法内部主要逻辑如下：
     * 1. 首先从事件对象中获取与订单相关的各种信息，包括购物车订单信息（ShopCartOrderDto）、订单参数（OrderParam）以及购物车商品项列表等。
     * 2. 获取当前用户的ID，用于后续根据用户ID和地址ID查询用户地址信息等操作。
     * 3. 根据订单参数中的地址ID和当前用户ID，通过UserAddrService获取用户地址信息，用于后续运费计算等操作（如果地址存在的话）。
     * 4. 遍历购物车商品项列表，针对每个商品项进行如下操作：
     *    - 通过ProductService和SkuService分别获取商品和商品规格的详细信息，若获取不到则抛出异常，表示购物车包含无法识别的商品。
     *    - 验证商品和商品规格的状态是否为上架状态（状态值为1），若不是则抛出商品已下架的异常。
     *    - 累加商品数量，计算商品总价（将每个商品项的金额累加到总金额中）。
     *    - 如果用户地址不为空，则调用TransportManagerService的方法计算该商品项对应的运费，并累加到总运费中。
     *    - 设置商品项的实际总价（这里暂设置为商品项原本的金额，可能后续还有其他逻辑调整），以及更新购物车订单信息中的各项金额相关属性（实际总价、总金额、商品总数量、运费等）。
     *
     * @param event 确认订单事件对象，包含了确认订单过程中涉及的各种相关数据，如购物车信息、订单参数等。
     */
    @EventListener(ConfirmOrderEvent.class)
    @Order(ConfirmOrderOrder.DEFAULT)
    public void defaultConfirmOrderEvent(ConfirmOrderEvent event) {

        // 从确认订单事件对象中获取购物车订单信息，包含了订单整体的一些汇总信息以及后续需要更新的金额等相关属性。
        ShopCartOrderDto shopCartOrderDto = event.getShopCartOrderDto();

        // 从确认订单事件对象中获取订单参数，其中包含了如用户选择的收货地址ID等关键信息，用于后续查询地址等操作。
        OrderParam orderParam = event.getOrderParam();

        // 通过SecurityUtils工具类获取当前用户的ID，用于后续根据用户ID来查询与之相关的用户地址等信息。
        String userId = SecurityUtils.getUser().getUserId();

        // 根据订单参数中的地址ID和当前用户ID，通过UserAddrService获取用户地址信息，该地址信息将用于计算运费等操作，若不存在地址则返回null。
        UserAddr userAddr = userAddrService.getUserAddrByUserId(orderParam.getAddrId(), userId);

        // 初始化订单总金额为0.0，后续会累加每个商品项的金额得到最终的订单总金额。
        double total = 0.0;

        // 初始化商品总数量为0，在遍历购物车商品项时会累加每个商品项的数量得到总的商品数量。
        int totalCount = 0;

        // 初始化运费为0.0，后续会根据每个商品项以及用户地址计算运费并累加得到总的运费。
        double transfee = 0.0;

        // 遍历购物车中的每个商品项，对每个商品项进行相关的信息获取、状态验证以及金额计算等操作。
        for (ShopCartItemDto shopCartItem : event.getShopCartItems()) {
            // 通过ProductService根据商品ID获取商品详细信息，用于后续验证商品状态以及参与金额计算等操作。
            Product product = productService.getProductByProdId(shopCartItem.getProdId());
            // 通过SkuService根据商品规格ID获取商品规格详细信息，同样用于验证商品状态和参与金额计算等操作。
            Sku sku = skuService.getSkuBySkuId(shopCartItem.getSkuId());
            if (product == null || sku == null) {
                throw new YamiShopBindException("购物车包含无法识别的商品");
            }
            if (product.getStatus()!= 1 || sku.getStatus()!= 1) {
                throw new YamiShopBindException("商品[" + sku.getProdName() + "]已下架");
            }

            // 累加商品数量，将当前商品项的数量累加到总商品数量中。
            totalCount = shopCartItem.getProdCount() + totalCount;
            // 使用Arith工具类（可能是自定义的用于精确计算的工具类）将当前商品项的金额累加到总金额中，得到订单的商品总金额。
            total = Arith.add(shopCartItem.getProductTotalAmount(), total);
            // 用户地址如果不为空，则表示用户设置了收货地址，需要计算该商品项对应的运费并累加到总运费中。
            if (userAddr!= null) {
                // 调用TransportManagerService的方法计算当前商品项对应的运费，传入商品项和用户地址信息作为参数，
                // 并将计算得到的运费累加到总运费中，实现运费的累加计算。
                transfee = Arith.add(transfee, transportManagerService.calculateTransfee(shopCartItem, userAddr));
            }

            // 设置商品项的实际总价（这里暂设置为商品项原本的金额，可能后续还有其他逻辑调整）。
            shopCartItem.setActualTotal(shopCartItem.getProductTotalAmount());
            // 更新购物车订单信息中的实际总价，将商品总金额和运费相加得到最终的实际总价。
            shopCartOrderDto.setActualTotal(Arith.add(total, transfee));
            // 更新购物车订单信息中的总金额，即商品总金额（不包含运费）。
            shopCartOrderDto.setTotal(total);
            // 更新购物车订单信息中的商品总数量。
            shopCartOrderDto.setTotalCount(totalCount);
            // 更新购物车订单信息中的运费。
            shopCartOrderDto.setTransfee(transfee);
        }
    }
}