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

package com.yami.shop.admin.controller;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.base.Objects;
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.UserAddrOrder;
import com.yami.shop.bean.param.DeliveryOrderParam;
import com.yami.shop.bean.param.OrderParam;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.security.admin.util.SecurityUtils;
import com.yami.shop.service.*;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Sheet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * 订单相关操作的控制器类，用于处理后台对订单的各种请求，例如分页查询、获取订单详情、订单发货、导出订单相关的Excel表格等操作。
 * @author lgh on 2018/09/15.
 */
@Slf4j
@RestController
@RequestMapping("/order/order")
public class OrderController {

    // 自动注入订单服务层接口，通过该接口调用具体的业务逻辑方法，处理订单相关业务
    @Autowired
    private OrderService orderService;
    // 自动注入订单项服务层接口，用于处理与订单中具体商品项相关的业务逻辑
    @Autowired
    private OrderItemService orderItemService;
    // 自动注入用户地址订单服务层接口，用于获取订单相关的用户地址信息等业务逻辑
    @Autowired
    private UserAddrOrderService userAddrOrderService;
    // 自动注入商品服务层接口，可能用于商品相关的业务操作，比如缓存清理等
    @Autowired
    private ProductService productService;
    // 自动注入库存单元（SKU）服务层接口，同样可能涉及到缓存清理等与库存单元相关的业务操作
    @Autowired
    private SkuService skuService;

    /**
     * 分页获取订单信息的方法
     * 此方法根据传入的订单查询参数（OrderParam）以及分页参数（PageParam），查询并返回符合条件的订单信息分页结果。
     * 只有拥有 'order:order:page' 权限的用户才能访问此方法。
     */
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('order:order:page')")
    public ServerResponseEntity<IPage<Order>> page(OrderParam orderParam, PageParam<Order> page) {
        // 获取当前登录用户所属店铺的 ID，用于后续筛选该店铺下的订单数据
        Long shopId = SecurityUtils.getSysUser().getShopId();
        // 将店铺 ID 设置到订单查询参数中，确保查询的是当前店铺的订单
        orderParam.setShopId(shopId);
        // 调用订单服务层的方法，按照订单参数和分页参数获取详细的订单分页信息
        IPage<Order> orderPage = orderService.pageOrdersDetailByOrderParam(page, orderParam);
        return ServerResponseEntity.success(orderPage);
    }

    /**
     * 根据订单编号获取订单详细信息的方法
     * 此方法接收一个订单编号，从数据库中获取对应的订单详细信息，包括订单项、用户地址等信息，并进行权限校验，
     * 只有拥有 'order:order:info' 权限且属于对应店铺的用户才能获取该订单信息。
     */
    @GetMapping("/orderInfo/{orderNumber}")
    @PreAuthorize("@pms.hasPermission('order:order:info')")
    public ServerResponseEntity<Order> info(@PathVariable("orderNumber") String orderNumber) {
        // 获取当前登录用户所属店铺的 ID，用于权限校验
        Long shopId = SecurityUtils.getSysUser().getShopId();
        // 通过订单服务层的方法，根据订单编号获取订单基本信息
        Order order = orderService.getOrderByOrderNumber(orderNumber);
        // 校验当前用户所属店铺 ID 是否与订单所属店铺 ID 一致，不一致则抛出无权限异常
        if (!Objects.equal(shopId, order.getShopId())) {
            throw new YamiShopBindException("您没有权限获取该订单信息");
        }
        // 通过订单项服务层的方法，根据订单编号获取该订单下的所有订单项信息
        List<OrderItem> orderItems = orderItemService.getOrderItemsByOrderNumber(orderNumber);
        // 将订单项信息设置到订单对象中，完善订单的详细信息
        order.setOrderItems(orderItems);
        // 通过用户地址订单服务层的方法，根据订单关联的地址 ID 获取用户地址信息
        UserAddrOrder userAddrOrder = userAddrOrderService.getById(order.getAddrOrderId());
        // 将用户地址信息设置到订单对象中
        order.setUserAddrOrder(userAddrOrder);
        return ServerResponseEntity.success(order);
    }

    /**
     * 处理订单发货的方法
     * 此方法接收发货相关的参数（DeliveryOrderParam），先进行权限校验，确保当前用户有权限修改对应订单信息，
     * 然后更新订单的发货相关信息，并在发货后清除对应商品和库存单元（SKU）的缓存，只有拥有 'order:order:delivery' 权限的用户才能执行此操作。
     */
    @PutMapping("/delivery")
    @PreAuthorize("@pms.hasPermission('order:order:delivery')")
    public ServerResponseEntity<Void> delivery(@RequestBody DeliveryOrderParam deliveryOrderParam) {
        // 获取当前登录用户所属店铺的 ID，用于权限校验
        Long shopId = SecurityUtils.getSysUser().getShopId();
        // 通过订单服务层的方法，根据订单编号获取订单基本信息
        Order order = orderService.getOrderByOrderNumber(deliveryOrderParam.getOrderNumber());
        // 校验当前用户所属店铺 ID 是否与订单所属店铺 ID 一致，不一致则抛出无权限异常
        if (!Objects.equal(shopId, order.getShopId())) {
            throw new YamiShopBindException("您没有权限修改该订单信息");
        }

        // 创建一个新的 Order 对象，用于设置要更新的订单信息
        Order orderParam = new Order();
        // 设置订单 ID，确保更新的是正确的订单记录
        orderParam.setOrderId(order.getOrderId());
        // 设置快递 ID，来自发货参数
        orderParam.setDvyId(deliveryOrderParam.getDvyId());
        // 设置快递单号，来自发货参数
        orderParam.setDvyFlowId(deliveryOrderParam.getDvyFlowId());
        // 设置发货时间为当前时间
        orderParam.setDvyTime(new Date());
        // 设置订单状态为已发货（通过 OrderStatus 枚举获取对应状态值）
        orderParam.setStatus(OrderStatus.CONSIGNMENT.value());
        // 设置用户 ID，与原订单一致
        orderParam.setUserId(order.getUserId());

        // 调用订单服务层的方法，更新订单的发货相关信息
        orderService.delivery(orderParam);

        // 通过订单项服务层的方法，根据订单编号获取该订单下的所有订单项信息
        List<OrderItem> orderItems = orderItemService.getOrderItemsByOrderNumber(deliveryOrderParam.getOrderNumber());
        // 遍历订单项，清除对应商品和库存单元（SKU）的缓存
        for (OrderItem orderItem : orderItems) {
            productService.removeProductCacheByProdId(orderItem.getProdId());
            skuService.removeSkuCacheBySkuId(orderItem.getSkuId(), orderItem.getProdId());
        }
        return ServerResponseEntity.success();
    }

    /**
     * 生成待发货订单的 Excel 表格并提供下载的方法
     * 此方法根据传入的订单筛选条件（包括时间范围、订单状态等）查询待发货的订单数据，
     * 然后使用 Hutool 的 Excel 工具类将数据整理并写入 Excel 表格，最后将表格输出到响应流中供客户端下载，
     * 只有拥有 'order:order:waitingConsignmentExcel' 权限的用户才能访问此方法。
     *
     * @param order 订单筛选条件对象，包含店铺 ID、订单状态等用于筛选订单的信息
     * @param startTime 订单创建时间范围的起始时间，格式需符合指定的 @DateTimeFormat 注解要求
     * @param endTime 订单创建时间范围的结束时间，格式需符合指定的 @DateTimeFormat 注解要求
     * @param consignmentName 发件人姓名，用于填充 Excel 表格中的发件人相关信息
     * @param consignmentMobile 发件人手机号，用于填充 Excel 表格中的发件人相关信息
     * @param consignmentAddr 发货地址，用于填充 Excel 表格中的发件人相关信息
     * @param response HttpServletResponse 对象，用于向客户端返回 Excel 文件流，实现下载功能
     */
    @GetMapping("/waitingConsignmentExcel")
    @PreAuthorize("@pms.hasPermission('order:order:waitingConsignmentExcel')")
    public void waitingConsignmentExcel(Order order, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
                                        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime, String consignmentName, String consignmentMobile,
                                        String consignmentAddr, HttpServletResponse response) {
        // 获取当前登录用户所属店铺的 ID，设置到订单筛选条件对象中，确保查询的是当前店铺的待发货订单
        Long shopId = SecurityUtils.getSysUser().getShopId();
        order.setShopId(shopId);
        // 设置订单状态为已支付（待发货状态对应的前置状态，这里假设已支付的订单待发货），用于筛选待发货订单
        order.setStatus(OrderStatus.PADYED.value());
        // 调用订单服务层的方法，根据订单筛选条件（包括时间范围、订单状态等）查询符合条件的待发货订单列表
        List<Order> orders = orderService.listOrdersDetailByOrder(order, startTime, endTime);

        // 通过 Hutool 的 ExcelUtil 工具类创建一个 ExcelWriter 对象，用于后续写入 Excel 数据
        ExcelWriter writer = ExcelUtil.getBigWriter();
        // 获取 Excel 表格的 Sheet 对象，用于设置列宽等表格样式相关操作
        Sheet sheet = writer.getSheet();
        // 设置各列的宽度，单位是字符宽度的 1/256，这里根据实际需求设置了不同列的宽度
        sheet.setColumnWidth(0, 20 * 256);
        sheet.setColumnWidth(1, 20 * 256);
        sheet.setColumnWidth(2, 20 * 256);
        sheet.setColumnWidth(3, 60 * 256);
        sheet.setColumnWidth(4, 60 * 256);
        sheet.setColumnWidth(7, 60 * 256);
        sheet.setColumnWidth(8, 60 * 256);
        sheet.setColumnWidth(9, 60 * 256);

        // 设置 Excel 表格的表头信息，定义了要展示的各列字段名称
        String[] hearder = {"订单编号", "收件人", "手机", "收货地址", "商品名称", "数量", "发件人姓名", "发件人手机号", "发货地址", "备注"};
        // 合并表头单元格，用于展示标题等整体说明信息，这里合并了除第一列外的所有表头列
        writer.merge(hearder.length - 1, "发货信息整理");
        // 将表头行数据写入 Excel 表格
        writer.writeRow(Arrays.asList(hearder));

        int row = 1;
        // 遍历查询到的每个订单
        for (Order dbOrder : orders) {
            // 获取订单对应的用户地址信息
            UserAddrOrder addr = dbOrder.getUserAddrOrder();
            // 拼接完整的收货地址信息
            String addrInfo = addr.getProvince() + addr.getCity() + addr.getArea() + addr.getAddr();
            // 获取该订单下的所有订单项信息
            List<OrderItem> orderItems = dbOrder.getOrderItems();
            row++;
            // 遍历每个订单项，将订单和订单项相关信息逐行写入 Excel 表格
            for (OrderItem orderItem : orderItems) {
                // 从第 0 列开始写入数据，列索引递增
                int col = 0;
                writer.writeCellValue(col++, row, dbOrder.getOrderNumber());
                writer.writeCellValue(col++, row, addr.getReceiver());
                writer.writeCellValue(col++, row, addr.getMobile());
                writer.writeCellValue(col++, row, addrInfo);
                writer.writeCellValue(col++, row, orderItem.getProdName());
                writer.writeCellValue(col++, row, orderItem.getProdCount());
                writer.writeCellValue(col++, row, consignmentName);
                writer.writeCellValue(col++, row, consignmentMobile);
                writer.writeCellValue(col++, row, consignmentAddr);
                writer.writeCellValue(col++, row, dbOrder.getRemarks());
            }
        }
        // 将整理好的 Excel 数据通过响应流输出给客户端，实现下载功能
        writeExcel(response, writer);
    }

    /**
     * 生成已销售订单的 Excel 表格并提供下载的方法
     * 此方法根据传入的订单筛选条件（包括时间范围、是否已支付等）查询已销售的订单数据，
     * 然后使用 Hutool 的 Excel 工具类将数据整理并写入 Excel 表格，最后将表格输出到响应流中供客户端下载，
     * 只有拥有 'order:order:soldExcel' 权限的用户才能访问此方法。
     *
     * @param order 订单筛选条件对象，包含店铺 ID、是否已支付等用于筛选订单的信息
     * @param startTime 订单创建时间范围的起始时间，格式需符合指定的 @DateTimeFormat 注解要求
     * @param endTime 订单创建时间范围的结束时间，格式需符合指定的 @DateTimeFormat 注解要求
     * @param response HttpServletResponse 对象，用于向客户端返回 Excel 文件流，实现下载功能
     */
    @GetMapping("/soldExcel")
    @PreAuthorize("@pms.hasPermission('order:order:soldExcel')")
    public void soldExcel(Order order, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
                          @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime, HttpServletResponse response) {
        // 获取当前登录用户所属店铺的 ID，设置到订单筛选条件对象中，确保查询的是当前店铺的已销售订单
        Long shopId = SecurityUtils.getSysUser().getShopId();
        order.setShopId(shopId);
        // 设置订单为已支付状态，用于筛选已销售的订单（这里简单以是否已支付作为已销售的判断条件之一）
        order.setIsPayed(1);
        // 调用订单服务层的方法，根据订单筛选条件（包括时间范围、是否已支付等）查询符合条件的已销售订单列表
        List<Order> orders = orderService.listOrdersDetailByOrder(order, startTime, endTime);

        // 通过 Hutool 的 ExcelUtil 工具类创建一个 ExcelWriter 对象，用于后续写入 Excel 数据
        ExcelWriter writer = ExcelUtil.getBigWriter();
        // 获取 Excel 表格的 Sheet 对象，用于设置列宽等表格样式相关操作
        Sheet sheet = writer.getSheet();
        // 设置各列的宽度，单位是字符宽度的 1/256，这里根据实际需求设置了不同列的宽度
        sheet.setColumnWidth(0, 20 * 256);
        sheet.setColumnWidth(1, 20 * 256);
        sheet.setColumnWidth(3, 20 * 256);
        sheet.setColumnWidth(4, 60 * 256);
        sheet.setColumnWidth(5, 60 * 256);

        // 设置 Excel 表格的表头信息，定义了要展示的各列字段名称
        String[] hearder = {"订单编号", "下单时间", "收件人", "手机", "收货地址", "商品名称", "数量", "订单应付", "订单运费", "订单实付"};
        // 合并表头单元格，用于展示标题等整体说明信息，这里合并了除第一列外的所有表头列
        writer.merge(hearder.length - 1, "销售信息整理");
        // 将表头行数据写入 Excel 表格
        writer.writeRow(Arrays.asList(hearder));

        int row = 1;
        // 遍历查询到的每个订单
        for (Order db