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

package com.yami.shop.api.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import com.google.common.collect.Lists;
import com.yami.shop.bean.app.dto.*;
import com.yami.shop.bean.app.param.ChangeShopCartParam;
import com.yami.shop.bean.app.param.ShopCartParam;
import com.yami.shop.bean.event.ShopCartEvent;
import com.yami.shop.bean.model.Basket;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.model.Sku;
import com.yami.shop.common.util.Arith;
import com.yami.shop.security.api.util.SecurityUtils;
import com.yami.shop.service.BasketService;
import com.yami.shop.service.ProductService;
import com.yami.shop.service.SkuService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationContext;
import com.yami.shop.common.response.ServerResponseEntity;
import org.springframework.web.bind.annotation.*;

import jakarta.validation.Valid;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 购物车相关接口的控制器类
 * 该类提供了多个与购物车操作相关的接口，包括获取购物车信息、删除购物车物品、清空购物车、添加/修改购物车物品、获取购物车商品数量、获取购物车失效商品信息、
 * 清空失效商品以及获取选中购物项总计和商品数量等功能，通过调用不同的服务层方法来实现相应的业务逻辑，并借助`Swagger`注解对接口进行文档化描述，方便接口的查看与使用。
 *
 * @author lanhai
 */
@RestController
@RequestMapping("/p/shopCart")
@Tag(name = "购物车接口")
// 使用 @AllArgsConstructor 注解，由 lombok 自动生成包含所有成员变量的构造函数，用于依赖注入
@AllArgsConstructor
public class ShopCartController {

    // 用于处理购物车相关业务逻辑的服务层对象，比如更新购物车、获取购物车商品项等操作
    private final BasketService basketService;
    // 用于处理商品相关业务逻辑的服务层对象，比如获取商品信息等操作
    private final ProductService productService;
    // 用于处理商品库存单元（SKU）相关业务逻辑的服务层对象，比如获取SKU信息等操作
    private final SkuService skuService;
    // Spring应用上下文对象，用于发布事件等操作，例如发布购物车相关事件
    private final ApplicationContext applicationContext;

    /**
     * 获取用户购物车信息的接口方法
     * 根据传入的以购物车ID为键的购物车参数对象映射表，先更新购物车信息（如果参数表不为空），然后获取购物车商品项列表，最后将其转换为购物车信息列表并返回给前端。
     *
     * @param basketIdShopCartParamMap 以购物车ID为键，对应的购物车参数对象为值的映射表，用于更新购物车相关信息，参数从请求体中获取。
     * @return 返回一个ServerResponseEntity<List<ShopCartDto>>类型的响应，成功时包含用户购物车信息对应的DTO列表数据，若获取或处理数据出现异常则返回相应的错误信息。
     */
    @PostMapping("/info")
    @Operation(summary = "获取用户购物车信息", description = "获取用户购物车信息，参数为用户选中的活动项数组,以购物车id为key")
    public ServerResponseEntity<List<ShopCartDto>> info(@RequestBody Map<Long, ShopCartParam> basketIdShopCartParamMap) {
        // 获取当前登录用户的ID，用于关联购物车与用户
        String userId = SecurityUtils.getUser().getUserId();

        // 如果传入的购物车参数映射表不为空，调用购物车服务层方法根据参数更新购物车信息
        if (MapUtil.isNotEmpty(basketIdShopCartParamMap)) {
            basketService.updateBasketByShopCartParam(userId, basketIdShopCartParamMap);
        }

        // 调用购物车服务层方法获取该用户的购物车商品项列表
        List<ShopCartItemDto> shopCartItems = basketService.getShopCartItems(userId);
        return ServerResponseEntity.success(basketService.getShopCarts(shopCartItems));
    }

    /**
     * 删除用户购物车物品的接口方法
     * 根据传入的购物车ID列表，调用购物车服务层方法删除对应购物车中的物品，并返回表示操作成功的响应给前端。
     *
     * @param basketIds 要删除物品的购物车的ID列表，参数从请求体中获取。
     * @return 返回一个ServerResponseEntity<Void>类型的响应，成功时表示删除购物车物品操作成功，无具体返回数据，若删除过程出现异常则返回相应的错误信息。
     */
    @DeleteMapping("/deleteItem")
    @Operation(summary = "删除用户购物车物品", description = "通过购物车id删除用户购物车物品")
    public ServerResponseEntity<Void> deleteItem(@RequestBody List<Long> basketIds) {
        // 获取当前登录用户的ID，用于关联购物车与用户
        String userId = SecurityUtils.getUser().getUserId();
        basketService.deleteShopCartItemsByBasketIds(userId, basketIds);
        return ServerResponseEntity.success();
    }

    /**
     * 清空用户购物车所有物品的接口方法
     * 调用购物车服务层方法清空当前登录用户购物车中的所有物品，并返回表示操作成功的响应给前端，响应中包含成功提示信息。
     *
     * @return 返回一个ServerResponseEntity<String>类型的响应，成功时表示清空购物车操作成功，返回的信息为"删除成功"，若操作出现异常则返回相应的错误信息。
     */
    @DeleteMapping("/deleteAll")
    @Operation(summary = "清空用户购物车所有物品", description = "清空用户购物车所有物品")
    public ServerResponseEntity<String> deleteAll() {
        // 获取当前登录用户的ID，用于关联购物车与用户
        String userId = SecurityUtils.getUser().getUserId();
        basketService.deleteAllShopCartItems(userId);
        return ServerResponseEntity.success("删除成功");
    }

    /**
     * 添加、修改用户购物车物品的接口方法
     * 根据传入的更改购物车参数对象，进行一系列验证（如更改数量是否为0、商品是否下架、库存是否足够等）后，调用购物车服务层方法添加或修改购物车中的商品，
     * 并根据不同情况返回相应的操作结果响应给前端。
     *
     * @param param 包含更改购物车商品相关信息的参数对象，如商品ID、SKU ID、店铺ID、更改的商品个数等信息，参数从请求体中获取且经过验证。
     * @return 返回一个ServerResponseEntity<String>类型的响应，根据操作结果返回不同信息，如添加成功、库存不足、商品已下架等提示信息，若操作出现异常则返回相应的错误信息。
     */
    @PostMapping("/changeItem")
    @Operation(summary = "添加、修改用户购物车物品", description = "通过商品id(prodId)、skuId、店铺Id(shopId),添加/修改用户购物车商品，并传入改变的商品个数(count)，" +
            "当count为正值时，增加商品数量，当count为负值时，将减去商品的数量，当最终count值小于0时，会将商品从购物车里面删除")
    public ServerResponseEntity<String> addItem(@Valid @RequestBody ChangeShopCartParam param) {

        // 如果更改的商品数量为0，则返回提示输入更改数量的失败响应
        if (param.getCount() == 0) {
            return ServerResponseEntity.showFailMsg("输入更改数量");
        }

        // 获取当前登录用户的ID，用于关联购物车与用户
        String userId = SecurityUtils.getUser().getUserId();
        // 获取当前用户的购物车商品项列表，用于后续判断商品是否已在购物车中等操作
        List<ShopCartItemDto> shopCartItems = basketService.getShopCartItems(userId);
        // 根据传入的商品ID获取商品信息
        Product prodParam = productService.getProductByProdId(param.getProdId());
        // 根据传入的SKU ID获取SKU信息
        Sku skuParam = skuService.getSkuBySkuId(param.getSkuId());

        // 当商品状态不正常（下架状态，状态值不为1）时，返回提示商品已下架的失败响应
        if (prodParam.getStatus()!= 1 || skuParam.getStatus()!= 1) {
            return ServerResponseEntity.showFailMsg("当前商品已下架");
        }

        // 遍历购物车商品项列表，判断要操作的商品是否已在购物车中
        for (ShopCartItemDto shopCartItemDto : shopCartItems) {
            if (Objects.equals(param.getSkuId(), shopCartItemDto.getSkuId())) {
                Basket basket = new Basket();
                basket.setUserId(userId);
                // 更新购物车中该商品的数量，为原数量加上传入的更改数量
                basket.setBasketCount(param.getCount() + shopCartItemDto.getProdCount());
                basket.setBasketId(shopCartItemDto.getBasketId());

                // 防止购物车商品数量变成负数，如果数量小于等于0，则删除该购物车商品项
                if (basket.getBasketCount() <= 0) {
                    basketService.deleteShopCartItemsByBasketIds(userId, Collections.singletonList(basket.getBasketId()));
                    return ServerResponseEntity.success();
                }

                // 当SKU实际库存不足（当前库存小于购物车中要设置的数量，且原购物车商品数量大于0，说明是修改操作）时，返回提示库存不足的失败响应
                if (skuParam.getStocks() < basket.getBasketCount() && shopCartItemDto.getProdCount() > 0) {
                    return ServerResponseEntity.showFailMsg("库存不足");
                }
                // 调用购物车服务层方法更新购物车商品项信息
                basketService.updateShopCartItem(basket);
                return ServerResponseEntity.success();
            }
        }

        // 如果商品不在购物车中，且传入的更改数量为负数，说明商品已被删除，返回相应提示的失败响应
        if (param.getCount() < 0) {
            return ServerResponseEntity.showFailMsg("商品已从购物车移除");
        }

        // 当SKU实际库存不足（当前库存小于要添加的数量，说明是添加操作）时，返回提示库存不足的失败响应
        if (skuParam.getStocks() < param.getCount()) {
            return ServerResponseEntity.showFailMsg("库存不足");
        }

        // 所有验证都通过时，调用购物车服务层方法添加购物车商品项，并返回添加成功的响应
        basketService.addShopCartItem(param, userId);
        return ServerResponseEntity.success("添加成功");
    }

    /**
     * 获取购物车商品数量的接口方法
     * 获取当前登录用户购物车中所有商品的数量总和，若购物车商品项列表为空则返回数量为0的成功响应，否则计算数量总和并返回相应的成功响应给前端。
     *
     * @return 返回一个ServerResponseEntity<Integer>类型的响应，成功时包含购物车商品数量总和，若获取数据出现异常则返回相应的错误信息。
     */
    @GetMapping("/prodCount")
    @Operation(summary = "获取购物车商品数量", description = "获取所有购物车商品数量")
    public ServerResponseEntity<Integer> prodCount() {
        // 获取当前登录用户的ID，用于关联购物车与用户
        String userId = SecurityUtils.getUser().getUserId();
        // 获取当前用户的购物车商品项列表
        List<ShopCartItemDto> shopCartItems = basketService.getShopCartItems(userId);
        if (CollectionUtil.isEmpty(shopCartItems)) {
            return ServerResponseEntity.success(0);
        }
        // 使用流操作计算购物车商品项列表中所有商品数量的总和
        Integer totalCount = shopCartItems.stream().map(ShopCartItemDto::getProdCount).reduce(0, Integer::sum);
        return ServerResponseEntity.success(totalCount);
    }

    /**
     * 获取购物车失效商品信息的接口方法
     * 获取当前登录用户购物车中的失效商品信息列表，先获取失效商品项列表，然后按照店铺ID进行分组整理，构建每个店铺对应的失效商品信息对象列表后返回给前端。
     *
     * @return 返回一个ServerResponseEntity<List<ShopCartExpiryItemDto>>类型的响应，成功时包含购物车失效商品信息对应的DTO列表数据，若获取数据出现异常则返回相应的错误信息。
     */
    @GetMapping("/expiryProdList")
    @Operation(summary = "获取购物车失效商品信息", description = "获取购物车失效商品列表")
    public ServerResponseEntity<List<ShopCartExpiryItemDto>> expiryProdList() {
        // 获取当前登录用户的ID，用于关联购物车与用户
        String userId = SecurityUtils.getUser().getUserId();
        // 获取当前用户购物车中的失效商品项列表
        List<ShopCartItemDto> shopCartItems = basketService.getShopCartExpiryItems(userId);
        // 根据店铺ID对失效商品项列表进行分组，得到以店铺ID为键，对应商品项列表为值的映射表
        Map<Long, List<ShopCartItemDto>> shopCartItemDtoMap = shopCartItems.stream().collect(Collectors.groupingBy(ShopCartItemDto::getShopId));

        // 用于存储构建好的每个店铺对应的购物车失效商品信息对象列表
        List<ShopCartExpiryItemDto> shopcartExpiryitems = Lists.newArrayList();

        // 遍历分组后的店铺ID集合，构建每个店铺对应的购物车失效商品信息对象，并添加到列表中
        for (Long key : shopCartItemDtoMap.keySet()) {
            ShopCartExpiryItemDto shopCartExpiryItemDto = new ShopCartExpiryItemDto();
            shopCartExpiryItemDto.setShopId(key);
            List<ShopCartItemDto> shopCartItemDtos = Lists.newArrayList();
            for (ShopCartItemDto tempShopCartItemDto : shopCartItemDtoMap.get(key)) {
                shopCartExpiryItemDto.setShopName(tempShopCartItemDto.getShopName());
                shopCartItemDtos.add(tempShopCartItemDto);
            }
            shopCartExpiryItemDto.setShopCartItemDtoList(shopCartItemDtos);
            shopcartExpiryitems.add(shopCartExpiryItemDto);
        }

        return ServerResponseEntity.success(shopcartExpiryitems);
    }

    /**
     * 清空用户失效商品的接口方法
     * 调用购物车服务层方法清空当前登录用户购物车中的失效商品，并返回表示操作成功的响应给前端。
     *
     * @return 返回一个ServerResponseEntity<Void>类型的响应，成功时表示清空失效商品操作成功，无具体返回数据，若操作出现异常则返回相应的错误信息。
     */
    @DeleteMapping("/cleanExpiryProdList")
    @Operation(summary = "清空用户失效商品", description = "清空用户失效商品")
    public ServerResponseEntity<Void> cleanExpiryProdList() {
        // 获取当前登录用户的ID，用于关联购物车与用户
        String userId = SecurityUtils.getUser().getUserId();
        basketService.cleanExpiryProdList(userId);
        return ServerResponseEntity.success();
    }

    /**
     * 获取选中购物项总计、选中的商品数量的接口方法
     * 根据传入的购物车ID列表，筛选出对应的购物车商品项，按照店铺ID进行分组后，计算选中商品的总计金额、商品数量等信息，构建相应的DTO对象并返回给前端。
     *
     * @param basketIds 要获取总计信息的购物车的ID列表，参数从请求体中获取。
     * @return 返回一个ServerResponseEntity<ShopCartAmountDto>类型的响应，成功时包含选中购物项总计、商品数量等信息对应的DTO对象，若获取或计算数据出现异常则返回相应的错误信息。
     */
    @PostMapping("/totalPay")
    @Operation(summary = "获取选中购物项总计、选中的商品数量", description = "获取选中购物项总计、选中的商品数量,参数为购物车id数组")
    public ServerResponseEntity<ShopCartAmountDto> getTotalPay(@RequestBody List<Long> basketIds) {

        // 拿到购物车的所有item
        List<ShopCartItemDto> dbShopCartItems = basketService.getShopCartItems(SecurityUtils.getUser().getUserId());

        // 从所有购物车商品项中筛选出传入的购物车ID列表对应的商品项
        List<ShopCartItemDto> chooseShopCartItems = dbShopCartItems
               .stream()
               .filter(shopCartItemDto -> {
                    for (Long basketId : basketIds) {
                        if (Objects.equals(basketId, shopCartItemDto.getBasketId())) {
                            return true;
                        }
                    }
                    return false;
                })
               .toList();