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

package com.yami.shop.api.controller;

import cn.hutool.core.collection.CollectionUtil;
import com.yami.shop.bean.app.dto.*;
import com.yami.shop.bean.app.param.OrderParam;
import com.yami.shop.bean.app.param.OrderShopParam;
import com.yami.shop.bean.app.param.SubmitOrderParam;
import com.yami.shop.bean.event.ConfirmOrderEvent;
import com.yami.shop.bean.model.Order;
import com.yami.shop.bean.model.UserAddr;
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.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import cn.hutool.core.bean.BeanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import com.yami.shop.common.response.ServerResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 订单相关接口的控制器类，主要处理订单生成、提交等核心业务操作，涉及与多个服务层交互以完成复杂的订单流程，
 * 例如获取用户地址、组装购物车商品信息、计算订单金额以及处理缓存等相关逻辑。
 *
 * @author lanhai
 */
@RestController
@RequestMapping("/p/order")
@Tag(name = "订单接口")
public class OrderController {

    // 自动注入订单服务层接口，用于处理订单相关的核心业务逻辑，如保存订单、查询订单等操作
    @Autowired
    private OrderService orderService;
    // 自动注入库存单元（SKU）服务层接口，可能用于处理商品库存相关操作，比如库存扣减、缓存清除等
    @Autowired
    private SkuService skuService;
    // 自动注入商品服务层接口，可用于商品相关的业务操作，例如获取商品信息、清除商品缓存等
    @Autowired
    private ProductService productService;
    // 自动注入用户地址服务层接口，用于获取用户地址相关信息，比如根据用户 ID 和地址 ID 查询具体地址详情
    @Autowired
    private UserAddrService userAddrService;
    // 自动注入购物车服务层接口，用于处理购物车相关业务，如获取购物车商品项、根据购物车商品组装店铺相关信息等
    @Autowired
    private BasketService basketService;
    // 自动注入 Spring 的应用上下文对象，用于发布事件，实现基于事件驱动的业务逻辑解耦，例如发布确认订单事件
    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 结算并生成订单信息的方法，此方法接收下单所需的参数，经过一系列业务逻辑处理后，生成并返回完整的订单信息，
     * 包括用户地址、各店铺的商品信息、订单金额等内容，同时会将生成的订单信息存入缓存（通过调用 orderService 的相关缓存方法）。
     *
     * @param orderParam 包含下单所需的各种参数的对象，通过请求体传入，且经过了参数验证（@Valid 注解），例如地址 ID、购物车商品项 ID 等信息
     * @return 包含完整订单信息的 ServerResponseEntity，以 ShopCartOrderMergerDto 类型封装，方便前端展示和后续业务处理，若购物车中无商品等情况则抛出相应异常
     */
    @PostMapping("/confirm")
    @Operation(summary = "结算，生成订单信息", description = "传入下单所需要的参数进行下单")
    public ServerResponseEntity<ShopCartOrderMergerDto> confirm(@Valid @RequestBody OrderParam orderParam) {
        // 获取当前用户的 ID，用于后续关联订单、地址等信息到该用户
        String userId = SecurityUtils.getUser().getUserId();

        // 根据传入的地址 ID 和用户 ID，从数据库中获取用户的订单地址信息，并转换为 DTO 类型方便后续返回给前端展示
        UserAddr userAddr = userAddrService.getUserAddrByUserId(orderParam.getAddrId(), userId);
        UserAddrDto userAddrDto = BeanUtil.copyProperties(userAddr, UserAddrDto.class);

        // 组装获取用户提交的购物车商品项，根据传入的购物车商品项 ID 和用户 ID 等信息，调用购物车服务层方法获取具体的商品项信息列表，
        // 如果购物车中没有选择商品（即列表为空），则抛出异常提示用户选择商品加入购物车
        List<ShopCartItemDto> shopCartItems = basketService.getShopCartItemsByOrderItems(orderParam.getBasketIds(), orderParam.getOrderItem(), userId);
        if (CollectionUtil.isEmpty(shopCartItems)) {
            throw new YamiShopBindException("请选择您需要的商品加入购物车");
        }

        // 根据店铺组装购物车中的商品信息，将购物车中的商品项按照店铺进行分类整理，返回每个店铺中的购物车商品信息列表，方便后续计算每个店铺的订单详情
        List<ShopCartDto> shopCarts = basketService.getShopCarts(shopCartItems);

        // 创建一个将要返回给前端的完整的订单信息对象，用于逐步组装并填充订单相关的各种信息
        ShopCartOrderMergerDto shopCartOrderMergerDto = new ShopCartOrderMergerDto();

        // 将用户地址信息设置到订单信息对象中，作为订单的收货地址相关信息
        shopCartOrderMergerDto.setUserAddr(userAddrDto);

        // 创建一个用于存放所有店铺的订单信息的列表，后续将每个店铺的订单信息对象添加到该列表中
        List<ShopCartOrderDto> shopCartOrders = new ArrayList<>();

        // 初始化订单相关的金额和数量变量，用于累加计算订单的总金额、实际支付金额、商品总数量以及优惠金额等信息
        double actualTotal = 0.0;
        double total = 0.0;
        int totalCount = 0;
        double orderReduce = 0.0;

        // 遍历每个店铺的购物车信息，进行每个店铺订单信息的组装和金额等相关计算
        for (ShopCartDto shopCart : shopCarts) {

            // 创建一个每个店铺的订单信息对象，用于填充该店铺相关的订单详细信息
            ShopCartOrderDto shopCartOrder = new ShopCartOrderDto();
            // 设置店铺 ID，明确该订单信息所属的店铺
            shopCartOrder.setShopId(shopCart.getShopId());
            // 设置店铺名称，方便前端展示等使用
            shopCartOrder.setShopName(shopCart.getShopName());

            // 获取该店铺中的商品项折扣信息列表，包含了商品的折扣相关情况以及对应的商品项列表等信息
            List<ShopCartItemDiscountDto> shopCartItemDiscounts = shopCart.getShopCartItemDiscounts();

            // 创建一个用于存放该店铺中所有商品项信息的列表，后续将从商品项折扣信息中提取出所有商品项并添加到该列表中
            List<ShopCartItemDto> shopAllShopCartItems = new ArrayList<>();
            // 遍历商品项折扣信息列表，将每个折扣信息中的商品项添加到店铺所有商品项列表中，实现商品项的整合
            for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartItemDiscounts) {
                List<ShopCartItemDto> discountShopCartItems = shopCartItemDiscount.getShopCartItems();
                shopAllShopCartItems.addAll(discountShopCartItems);
            }

            // 将商品项折扣信息设置到店铺订单信息对象中，作为该店铺订单的商品项折扣相关内容
            shopCartOrder.setShopCartItemDiscounts(shopCartItemDiscounts);

            // 发布确认订单事件，将店铺订单信息、下单参数以及店铺所有商品项信息作为事件参数传递出去，
            // 可以通过事件监听器实现一些额外的业务逻辑，例如基于事件驱动的业务流程扩展、通知等功能，实现业务逻辑的解耦
            applicationContext.publishEvent(new ConfirmOrderEvent(shopCartOrder, orderParam, shopAllShopCartItems));

            // 累加计算订单的实际支付金额，将当前店铺订单的实际支付金额累加到总实际支付金额中
            actualTotal = Arith.add(actualTotal, shopCartOrder.getActualTotal());
            // 累加计算订单的总金额，将当前店铺订单的总金额累加到总金额中
            total = Arith.add(total, shopCartOrder.getTotal());
            // 累加计算商品的总数量，将当前店铺订单的商品数量累加到总数量中
            totalCount = totalCount + shopCartOrder.getTotalCount();
            // 累加计算订单的优惠金额，将当前店铺订单的优惠金额累加到总优惠金额中
            orderReduce = Arith.add(orderReduce, shopCartOrder.getShopReduce());

            // 将当前店铺的订单信息对象添加到所有店铺订单信息列表中，完成一个店铺订单信息的组装
            shopCartOrders.add(shopCartOrder);
        }

        // 将计算好的订单总金额、实际支付金额、商品总数量以及优惠金额等信息设置到完整的订单信息对象中
        shopCartOrderMergerDto.setActualTotal(actualTotal);
        shopCartOrderMergerDto.setTotal(total);
        shopCartOrderMergerDto.setTotalCount(totalCount);
        shopCartOrderMergerDto.setShopCartOrders(shopCartOrders);
        shopCartOrderMergerDto.setOrderReduce(orderReduce);

        // 将组装好的完整订单信息存入缓存中，并返回缓存后的订单信息对象，方便后续提交订单等操作使用缓存数据，提高性能并保证数据一致性
        shopCartOrderMergerDto = orderService.putConfirmOrderCache(userId, shopCartOrderMergerDto);

        return ServerResponseEntity.success(shopCartOrderMergerDto);
    }

    /**
     * 购物车/立即购买提交订单并根据店铺拆单的方法，此方法接收提交订单的参数，先从缓存中获取之前生成的订单信息，
     * 然后根据传入的店铺参数设置订单备注信息，接着调用订单服务层方法提交订单并获取生成的订单列表，之后处理订单编号拼接、
     * 商品缓存清除以及购物车缓存清除（根据是否为购物车提交订单判断）等操作，最后返回包含订单编号字符串的响应结果给前端，
     * 若缓存中不存在订单信息则抛出订单已过期的异常提示用户重新下单。
     *
     * @param submitOrderParam 包含提交订单所需的各种参数的对象，通过请求体传入，且经过了参数验证（@Valid 注解），例如店铺参数、备注信息等
     * @return 包含订单编号字符串的 ServerResponseEntity，以 OrderNumbersDto 类型封装，方便前端获取订单编号进行后续支付等操作，若订单已过期等情况则抛出相应异常
     */
    @PostMapping("/submit")
    @Operation(summary = "提交订单，返回支付流水号", description = "根据传入的参数判断是否为购物车提交订单，同时对购物车进行删除，用户开始进行支付")
    public ServerResponseEntity<OrderNumbersDto> submitOrders(@Valid @RequestBody SubmitOrderParam submitOrderParam) {
        // 获取当前用户的 ID，用于后续从缓存中获取订单信息、关联订单等操作到该用户
        String userId = SecurityUtils.getUser().getUserId();
        // 从缓存中获取之前生成并缓存的确认订单信息，如果缓存中不存在则抛出异常提示订单已过期，需要重新下单
        ShopCartOrderMergerDto mergerOrder = orderService.getConfirmOrderCache(userId);
        if (mergerOrder == null) {
            throw new YamiShopBindException("订单已过期，请重新下单");
        }

        // 获取提交订单参数中的店铺参数列表，用于后续设置订单的备注信息等操作
        List<OrderShopParam> orderShopParams = submitOrderParam.getOrderShopParam();

        // 获取缓存中订单信息里的所有店铺订单信息列表，用于遍历设置备注信息以及后续的缓存清除等相关操作
        List<ShopCartOrderDto> shopCartOrders = mergerOrder.getShopCartOrders();

        // 设置订单备注信息，如果传入的店铺参数列表不为空，则遍历店铺订单信息和店铺参数，根据店铺 ID 匹配，将对应的备注信息设置到店铺订单对象中
        if (CollectionUtil.isNotEmpty(orderShopParams)) {
            for (ShopCartOrderDto shopCartOrder : shopCartOrders) {
                for (OrderShopParam orderShopParam : orderShopParams) {
                    if (Objects.equals(shopCartOrder.getShopId(), orderShopParam.getShopId())) {
                        shopCartOrder.setRemarks(orderShopParam.getRemarks());
                    }
                }
            }
        }

        // 调用订单服务层的提交订单方法，传入用户 ID 和缓存中的订单信息对象，提交订单并获取生成的订单列表，该方法内部会进行复杂的业务逻辑处理，
        // 例如根据店铺拆单、保存订单信息到数据库、处理库存扣减等相关操作
        List<Order> orders = orderService.submit(userId, mergerOrder);

        // 创建一个字符串构建器，用于拼接订单编号，方便后续返回给前端一个用逗号分隔的订单编号字符串
        StringBuilder orderNumbers = new StringBuilder();
        // 遍历生成的订单列表，将每个订单的编号添加到字符串构建器中，并在每个编号后添加逗号进行分隔
        for (Order order : orders) {
            orderNumbers.append(order.getOrderNumber()).append(",");
        }
        // 删除最后一个多余的逗号，得到正确格式的订单编号字符串
        orderNumbers.deleteCharAt(orderNumbers.length() - 1);

        // 标记是否为购物车提交订单，初始化为 false，后续根据购物车商品项的 ID 判断是否为购物车提交订单情况
        boolean isShopCartOrder = false;

        // 移除相关缓存，遍历每个店铺的订单信息、商品项折扣信息以及商品项信息，进行以下操作：
        // 1. 判断商品项是否有购物车 ID（如果有则说明是购物车提交订单），并相应地设置标记变量。
        // 2. 清除商品对应的库存单元（SKU）缓存，通过调用 SKU 服务层的方法，传入商品的 SKU ID 和商品 ID。
        // 3. 清除商品缓存，通过调用商品服务层的方法，传入商品 ID，确保缓存数据与数据库最新状态一致，避免数据不一致问题影响后续业务操作。
        for (ShopCartOrderDto shopCartOrder : shopCartOrders) {
            for (ShopCartItemDiscountDto shopCartItemDiscount : shopCartOrder.getShopCartItemDiscounts()) {
                for (ShopCartItemDto shopCartItem : shopCartItemDiscount.getShopCartItems()) {
                    Long basketId = shopCartItem.getBasketId();
                    if (basketId!= null && basketId!= 0) {
                        isShopCartOrder = true;
                    }
                    skuService.removeSkuCacheBySkuId(shopCartItem.getSkuId(), shopCartItem.getProdId());
                    productService.removeProductCacheByProdId(shopCartItem.getProdId());
                }
            }
        }

        // 如果是购物车提交订单（即标记变量为 true），则调用购物车服务层的方法，根据用户 ID 清除该用户的购物车商品项缓存，保证购物车数据的准确性
        if (isShopCartOrder) {
            basketService.removeShopCartItemsCacheByUserId(userId);
        }

        // 清除确认订单的缓存，通过调用订单服务层的方法，传入用户 ID，确保缓存数据与实际业务状态一致，避免缓存数据干扰下次下单操作
        orderService.removeConfirmOrderCache(userId);

        return ServerResponseEntity.success(new OrderNumbersDto(orderNumbers.toString()));
    }
}