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

// 该类所属的包名，表明其位于商城API的控制器包下，主要用于处理与“我的订单”相关的接口请求及业务逻辑。
package com.yami.shop.api.controller;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.app.dto.*;
import com.yami.shop.bean.enums.OrderStatus;
import com.yami.shop.bean.model.Order;
import com.yami.shop.bean.model.OrderItem;
import com.yami.shop.bean.model.ShopDetail;
import com.yami.shop.bean.model.UserAddrOrder;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.Arith;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.security.api.util.SecurityUtils;
import com.yami.shop.service.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * MyOrderController类是一个Spring RESTful风格的控制器，主要用于处理与“我的订单”相关的各种操作接口，
 * 涵盖了获取订单详情、订单列表查询、取消订单、确认收货、删除订单以及获取订单数量等功能，为前端应用提供了相应的后端服务支持。
 *
 * @author lanhai
 */
@RestController
// 定义该控制器类的基础请求路径，所有该类中的接口请求路径都将以此为前缀，表明是与“我的订单”相关的操作接口。
@RequestMapping("/p/myOrder")
// 使用Swagger的@Tag注解对该控制器类进行标记，用于在API文档中生成对应的分类标签，方便接口文档的分类展示和阅读，这里表示该类下的接口都属于“我的订单接口”这一分类。
@Tag(name = "我的订单接口")
// 使用lombok的@AllArgsConstructor注解，自动生成包含所有final字段的构造函数，方便依赖注入，这里会为所有注入的服务类生成对应的构造函数参数。
@AllArgsConstructor
public class MyOrderController {

    // 通过构造函数注入OrderService实例，用于调用与订单相关的业务逻辑方法，比如获取订单、取消订单、确认收货等操作。
    private final OrderService orderService;

    // 通过构造函数注入UserAddrOrderService实例，用于处理用户地址与订单相关的业务逻辑，可能涉及获取订单对应的用户地址等操作。
    private final UserAddrOrderService userAddrOrderService;

    // 通过构造函数注入ProductService实例，用于操作产品相关的业务逻辑，例如可能在某些操作后需要清除产品缓存等情况会用到该服务。
    private final ProductService productService;

    // 通过构造函数注入SkuService实例，用于处理商品库存单元（SKU）相关的业务逻辑，比如根据SKU ID进行缓存清除等操作。
    private final SkuService skuService;

    // 通过构造函数注入MyOrderService实例，用于获取与“我的订单”相关的分页数据等业务逻辑，例如根据用户ID和订单状态查询订单列表等操作。
    private final MyOrderService myOrderService;

    // 通过构造函数注入ShopDetailService实例，用于获取店铺详情信息，比如在获取订单详情时需要展示店铺名称等信息时会调用该服务获取相应数据。
    private final ShopDetailService shopDetailService;

    // 通过构造函数注入OrderItemService实例，用于获取订单商品项相关的业务逻辑，例如根据订单号获取订单中包含的具体商品项信息等操作。
    private final OrderItemService orderItemService;

    /**
     * 订单详情信息接口方法。
     * 根据传入的订单号（orderNumber），获取对应的订单详情信息，包括店铺信息、用户地址信息、订单商品项信息等，并进行相应的数据组装和计算，
     * 最后将完整的订单详情信息封装在ServerResponseEntity中返回，用于统一的接口响应格式处理。
     *
     * @param orderNumber 要查询详情的订单的唯一标识符，通过请求参数传入，是必须提供的参数，用于定位具体的订单记录。
     * @return 返回包含订单详情信息的ServerResponseEntity，成功时其数据部分为OrderShopDto类型，该类型包含了订单的各种详细信息，如店铺信息、商品信息、总价等。
     */
    @GetMapping("/orderDetail")
    // 使用Swagger的@Operation注解对该接口方法进行描述，用于在API文档中生成对应的接口说明信息，这里简要说明了该接口的功能是获取订单详情信息。
    @Operation(summary = "订单详情信息", description = "根据订单号获取订单详情信息")
    // 使用Swagger的@Parameter注解对接口方法的参数进行描述，这里指定了orderNumber参数的名称、描述以及其为必填项等信息，方便在API文档中展示参数详情。
    @Parameter(name = "orderNumber", description = "订单号", required = true)
    public ServerResponseEntity<OrderShopDto> orderDetail(@RequestParam(value = "orderNumber") String orderNumber) {

        // 通过SecurityUtils获取当前登录用户的ID，用于后续验证用户是否有权限获取该订单信息等操作。
        String userId = SecurityUtils.getUser().getUserId();
        OrderShopDto orderShopDto = new OrderShopDto();

        // 调用OrderService的getOrderByOrderNumber方法，根据订单号获取对应的订单对象，如果订单不存在则抛出异常。
        Order order = orderService.getOrderByOrderNumber(orderNumber);
        if (order == null) {
            throw new RuntimeException("该订单不存在");
        }
        // 验证当前登录用户是否有权限获取该订单信息，通过比较订单中的用户ID和当前登录用户的ID是否一致来判断，如果不一致则抛出异常。
        if (!Objects.equals(order.getUserId(), userId)) {
            throw new RuntimeException("你没有权限获取该订单信息");
        }

        // 调用ShopDetailService的getShopDetailByShopId方法，根据订单所属店铺的ID获取店铺详情信息，用于填充订单详情中的店铺相关信息。
        ShopDetail shopDetail = shopDetailService.getShopDetailByShopId(order.getShopId());
        // 调用UserAddrOrderService的getById方法，根据订单关联的地址订单ID获取对应的用户地址订单对象，用于获取用户地址信息。
        UserAddrOrder userAddrOrder = userAddrOrderService.getById(order.getAddrOrderId());
        // 使用BeanUtil将UserAddrOrder对象的属性复制到UserAddrDto对象中，进行数据类型转换，方便后续统一处理和返回数据。
        UserAddrDto userAddrDto = BeanUtil.copyProperties(userAddrOrder, UserAddrDto.class);
        // 调用OrderItemService的getOrderItemsByOrderNumber方法，根据订单号获取该订单下包含的所有商品项信息列表。
        List<OrderItem> orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber);
        // 使用BeanUtil将List<OrderItem>中的每个OrderItem对象的属性复制到OrderItemDto对象中，组成新的列表，用于后续统一处理和返回数据。
        List<OrderItemDto> orderItemList = BeanUtil.copyToList(orderItems, OrderItemDto.class);

        // 将获取到的店铺相关信息设置到OrderShopDto对象中，如店铺ID、店铺名称等。
        orderShopDto.setShopId(shopDetail.getShopId());
        orderShopDto.setShopName(shopDetail.getShopName());
        // 设置订单的实际总价、用户地址信息、订单商品项信息、运费、优惠金额、创建时间、备注以及订单状态等信息到OrderShopDto对象中。
        orderShopDto.setActualTotal(order.getActualTotal());
        orderShopDto.setUserAddrDto(userAddrDto);
        orderShopDto.setOrderItemDtos(orderItemList);
        orderShopDto.setTransfee(order.getFreightAmount());
        orderShopDto.setReduceAmount(order.getReduceAmount());
        orderShopDto.setCreateTime(order.getCreateTime());
        orderShopDto.setRemarks(order.getRemarks());
        orderShopDto.setStatus(order.getStatus());

        // 计算订单中商品的总金额和商品总数量，通过遍历订单商品项列表，累加每个商品项的商品总金额和商品数量来实现。
        double total = 0.0;
        Integer totalNum = 0;
        for (OrderItemDto orderItem : orderShopDto.getOrderItemDtos()) {
            total = Arith.add(total, orderItem.getProductTotalAmount());
            totalNum += orderItem.getProdCount();
        }
        // 将计算得到的商品总金额和商品总数量设置到OrderShopDto对象中。
        orderShopDto.setTotal(total);
        orderShopDto.setTotalNum(totalNum);

        return ServerResponseEntity.success(orderShopDto);
    }

    /**
     * 订单列表接口方法。
     * 根据传入的订单状态（status）以及分页参数（page），获取当前登录用户对应的订单列表信息，按照指定的状态进行筛选，
     * 如果状态为0则获取所有订单，最后将获取到的分页订单列表信息封装在ServerResponseEntity中返回，用于统一的接口响应格式处理。
     *
     * @param status 订单状态参数，通过请求参数传入，用于筛选符合指定状态的订单，其取值对应不同的订单状态，如1表示待付款、2表示待发货等。
     * @param page 分页参数对象，包含页码、每页数量等信息，用于进行分页查询操作，通过该参数可以获取指定页的订单列表数据。
     * @return 返回包含分页订单列表信息的ServerResponseEntity，成功时其数据部分为IPage<MyOrderDto>类型，该类型包含了分页后的订单列表数据以及分页相关的元数据信息。
     */
    @GetMapping("/myOrder")
    @Operation(summary = "订单列表信息", description = "根据订单状态获取订单列表信息，状态为0时获取所有订单")
    @Parameters({
            @Parameter(name = "status", description = "订单状态 1:待付款 2:待发货 3:待收货 4:待评价 5:成功 6:失败")
    })
    public ServerResponseEntity<IPage<MyOrderDto>> myOrder(@RequestParam(value = "status") Integer status, PageParam<MyOrderDto> page) {
        // 通过SecurityUtils获取当前登录用户的ID，用于查询该用户对应的订单列表信息。
        String userId = SecurityUtils.getUser().getUserId();
        // 调用MyOrderService的pageMyOrderByUserIdAndStatus方法，根据用户ID和订单状态进行分页查询订单列表信息，获取到IPage<MyOrderDto>类型的分页数据对象。
        IPage<MyOrderDto> myOrderDtoIpage = myOrderService.pageMyOrderByUserIdAndStatus(page, userId, status);
        return ServerResponseEntity.success(myOrderDtoIpage);
    }

    /**
     * 取消订单接口方法。
     * 根据传入的订单号（orderNumber），先验证当前登录用户是否有权限取消该订单以及订单是否处于可取消状态（未支付状态），
     * 如果验证通过，则获取该订单下的所有商品项信息，设置到订单对象中，然后调用OrderService的cancelOrders方法取消订单，
     * 最后在取消订单后，根据订单商品项信息清除对应的产品缓存和SKU缓存，返回表示操作成功的ServerResponseEntity对象，用于统一的接口响应格式处理。
     *
     * @param orderNumber 要取消的订单的唯一标识符，通过路径变量传入，是必须提供的参数，用于定位具体的订单记录。
     * @return 返回表示操作成功的ServerResponseEntity，无具体数据内容（这里返回的是一个表示成功的空字符串，仅用于表示操作成功的状态），其数据部分为String类型。
     */
    @PutMapping("/cancel/{orderNumber}")
    @Operation(summary = "根据订单号取消订单", description = "根据订单号取消订单")
    @Parameter(name = "orderNumber", description = "订单号", required = true)
    public ServerResponseEntity<String> cancel(@PathVariable("orderNumber") String orderNumber) {
        // 通过SecurityUtils获取当前登录用户的ID，用于验证用户是否有权限操作该订单。
        String userId = SecurityUtils.getUser().getUserId();
        // 调用OrderService的getOrderByOrderNumber方法，根据订单号获取对应的订单对象。
        Order order = orderService.getOrderByOrderNumber(orderNumber);
        if (!Objects.equals(order.getUserId(), userId)) {
            throw new YamiShopBindException("你没有权限获取该订单信息");
        }
        if (!Objects.equals(order.getStatus(), OrderStatus.UNPAY.value())) {
            throw new YamiShopBindException("订单已支付，无法取消订单");
        }
        // 调用OrderItemService的getOrderItemsByOrderNumber方法，根据订单号获取该订单下包含的所有商品项信息列表。
        List<OrderItem> orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber);
        order.setOrderItems(orderItems);
        // 调用OrderService的cancelOrders方法，传入包含该订单的列表（这里使用Collections.singletonList将单个订单包装成列表），取消订单操作。
        orderService.cancelOrders(Collections.singletonList(order));

        // 循环遍历订单商品项信息列表，针对每个商品项，调用ProductService的removeProductCacheByProdId方法清除对应产品的缓存，
        // 以及调用SkuService的removeSkuCacheBySkuId方法清除对应SKU的缓存，以保证数据的一致性和缓存的有效性。
        for (OrderItem orderItem : orderItems) {
            productService.removeProductCacheByProdId(orderItem.getProdId());
            skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId());
        }
        return ServerResponseEntity.success();
    }

    /**
     * 确认收货接口方法。
     * 根据传入的订单号（orderNumber），先验证当前登录用户是否有权限确认该订单以及订单是否处于可确认收货状态（已发货状态），
     * 如果验证通过，则获取该订单下的所有商品项信息，设置到订单对象中，然后调用OrderService的confirmOrder方法确认收货，
     * 最后根据订单商品项信息清除对应的产品缓存和SKU缓存，返回表示操作成功的ServerResponseEntity对象，用于统一的接口响应格式处理。
     *
     * @param orderNumber 要确认收货的订单的唯一标识符，通过路径变量传入，是必须提供的参数，用于定位具体的订单记录。
     * @return 返回表示操作成功的ServerResponseEntity，无具体数据内容（这里返回的是一个表示成功的空字符串，仅用于表示操作成功的状态），其数据部分为String类型。
     */
    @PutMapping("/receipt/{orderNumber}")
    @Operation(summary = "根据订单号确认收货", description = "根据订单号确认收货")
    public ServerResponseEntity<String> receipt(@PathVariable("orderNumber") String orderNumber) {
        // 通过SecurityUtils获取当前登录用户的ID，用于验证用户是否有权限操作该订单。
        String userId = SecurityUtils.getUser().getUserId();
        // 调用OrderService的getOrderByOrderNumber方法，根据订单号获取对应的订单对象。
        Order order = orderService.getOrderByOrderNumber(orderNumber);
        if (!Objects.equals(order.getUserId(), userId)) {
            throw new YamiShopBindException("你没有权限获取该订单信息");
        }
        if (!Objects.equals(order.getStatus(), OrderStatus.CONSIGNMENT.value())) {
            throw new YamiShopBindException("订单未发货，无法确认收货");
        }
        // 调用OrderItemService的getOrderItemsByOrderNumber方法，根据订单号获取该订单下包含的所有商品项信息列表。
        List<OrderItem> orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber);
        order.setOrderItems(orderItems);
        // 调用OrderService的confirmOrder方法，传入包含该订单的列表（这里使用Collections.singletonList将单个订单包装成列表），进行确认收货操作。
        orderService.confirmOrder(Collections.singletonList(order));

        for (OrderItem orderItem : orderItems) {
            productService.removeProductCacheByProdId(orderItem.getProdId());
            skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId());
        }
        return ServerResponseEntity.success();
    }

    /**
     * 删除订单接口方法。
     * 根据传入的订单号（orderNumber），先验证订单是否存在、当前登录用户是否有权限删除该订单以及订单是否处于可删除状态（已完成或已关闭状态），
     * 如果验证通过，则调用OrderService的deleteOrders方法删除订单，最后返回表示操作成功的ServerResponseEntity对象，用于统一的接口响应格式处理，
     * 其返回的数据部分包含表示删除成功的提示信息字符串。
     *
     * @param orderNumber 要删除的订单的唯一标识符，通过路径变量传入，是必须提供的参数，用于定位具体的订单记录。
     * @return 返回表示操作成功的ServerResponseEntity，其数据部分为