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

package com.yami.shop.admin.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yami.shop.bean.model.Product;
import com.yami.shop.bean.model.Sku;
import com.yami.shop.bean.param.ProductParam;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.common.util.Json;
import com.yami.shop.common.util.PageParam;
import com.yami.shop.security.admin.util.SecurityUtils;
import com.yami.shop.service.BasketService;
import com.yami.shop.service.ProdTagReferenceService;
import com.yami.shop.service.ProductService;
import com.yami.shop.service.SkuService;
import cn.hutool.core.bean.BeanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

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

/**
 * 商品列表、商品发布相关的控制器类，主要负责处理商品的各种操作，包括分页查询、获取商品详细信息、保存、修改、删除商品以及更新商品状态等功能，
 * 同时在各个操作方法上添加了相应的权限控制，并且在部分操作中涉及到对相关附属数据（如商品规格、标签、购物车缓存等）的处理逻辑。
 *
 * @author lgh
 */
@RestController
@RequestMapping("/prod/prod")
public class ProductController {

    // 注入ProductService，用于处理与商品相关的核心业务逻辑，例如商品信息的查询、保存、更新、删除等操作。
    @Autowired
    private ProductService productService;

    // 注入SkuService，用于处理商品规格（Sku）相关的业务逻辑，比如根据商品ID获取商品规格列表等操作，在商品相关操作中常涉及规格信息的处理。
    @Autowired
    private SkuService skuService;

    // 注入ProdTagReferenceService，用于获取商品与标签关联相关的业务逻辑，例如获取某个商品关联的标签ID列表等操作，用于处理商品的分组标签信息。
    @Autowired
    private ProdTagReferenceService prodTagReferenceService;

    // 注入BasketService，用于处理购物车相关的业务逻辑，在商品操作涉及影响购物车缓存的场景下（如商品修改、删除等）会调用其方法来清除相关缓存，保证数据一致性。
    @Autowired
    private BasketService basketService;

    /**
     * 分页获取商品信息的方法，根据传入的查询条件（ProductParam对象）和分页参数（PageParam对象），
     * 通过ProductService进行分页查询，可根据商品名称进行模糊匹配（如果名称不为空），筛选出当前店铺（根据当前登录用户所属店铺ID确定）下的商品，
     * 还可根据商品状态进行筛选，并按照上架时间倒序排序，最后将查询到的分页结果封装在成功的响应实体中返回给前端。
     * 通过@PreAuthorize注解进行权限控制，只有具备"prod:prod:page"权限的用户才能访问此方法。
     *
     * @param product 包含查询条件的ProductParam对象，可设置商品名称、状态等筛选条件。
     * @param page 分页参数对象，用于指定页码、每页数量等分页相关信息。
     * @return 返回包含分页后的商品信息的ServerResponseEntity对象，若查询成功则响应体中包含符合条件的分页商品数据，否则返回相应错误信息。
     */
    @GetMapping("/page")
    @PreAuthorize("@pms.hasPermission('prod:prod:page')")
    public ServerResponseEntity<IPage<Product>> page(ProductParam product, PageParam<Product> page) {
        // 使用ProductService的page方法进行分页查询，传入分页参数和LambdaQueryWrapper构建的查询条件。
        // 通过StrUtil.isNotBlank判断商品名称是否不为空，若不为空则添加模糊查询条件，按照商品名称进行模糊匹配，同时根据店铺ID和商品状态（如果不为空）进行筛选，最后按照上架时间倒序排序。
        IPage<Product> products = productService.page(page,
                new LambdaQueryWrapper<Product>()
                     .like(StrUtil.isNotBlank(product.getProdName()), Product::getProdName, product.getProdName())
                     .eq(Product::getShopId, SecurityUtils.getSysUser().getShopId())
                     .eq(product.getStatus()!= null, Product::getStatus, product.getStatus())
                     .orderByDesc(Product::getPutawayTime));
        return ServerResponseEntity.success(products);
    }

    /**
     * 根据商品ID获取商品详细信息的方法，通过传入的商品ID，调用ProductService的getProductByProdId方法从数据库中查询对应的商品对象，
     * 首先判断当前登录用户所属的店铺ID与商品所属的店铺ID是否一致，若不一致则抛出无权限异常，若有权限则获取该商品的规格列表（通过SkuService）并设置到商品对象中，
     * 同时获取该商品关联的分组标签ID列表（通过ProdTagReferenceService）并设置到商品对象中，最后将包含详细信息的商品对象封装在成功的响应实体中返回给前端。
     * 通过@PreAuthorize注解进行权限控制，只有具备"prod:prod:info"权限的用户才能访问此方法。
     *
     * @param prodId 要获取详细信息的商品的唯一标识符。
     * @return 返回包含商品详细信息的ServerResponseEntity对象，若获取成功则响应体中包含对应的商品对象及其规格列表、分组标签ID列表等信息，否则返回相应错误信息。
     */
    @GetMapping("/info/{prodId}")
    @PreAuthorize("@pms.hasPermission('prod:prod:info')")
    public ServerResponseEntity<Product> info(@PathVariable("prodId") Long prodId) {
        Product prod = productService.getProductByProdId(prodId);
        if (!Objects.equals(prod.getShopId(), SecurityUtils.getSysUser().getShopId())) {
            throw new YamiShopBindException("没有权限获取该商品规格信息");
        }
        List<Sku> skuList = skuService.listByProdId(prodId);
        prod.setSkuList(skuList);

        // 获取分组标签，通过ProdTagReferenceService的listTagIdByProdId方法获取该商品关联的标签ID列表，并设置到商品对象中。
        List<Long> listTagId = prodTagReferenceService.listTagIdByProdId(prodId);
        prod.setTagList(listTagId);
        return ServerResponseEntity.success(prod);
    }

    /**
     * 保存商品信息的方法，接收一个经过验证（通过@Valid注解）的ProductParam对象作为请求体，代表要保存的商品信息。
     * 首先调用私有方法checkParam对传入的商品参数进行合法性校验，然后将ProductParam对象的属性复制到Product对象中（通过BeanUtil），
     * 设置商品的配送方式（将相关对象转换为JSON字符串保存）、所属店铺ID（通过当前登录用户所属店铺ID确定）以及创建时间、更新时间等信息，
     * 若商品状态为上架状态（状态值为1）则设置上架时间为当前时间，最后调用ProductService的saveProduct方法将商品信息保存到数据库中，
     * 并返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制，只有具备"prod:prod:save"权限的用户才能访问此方法。
     *
     * @param productParam 包含要保存的商品信息的请求体对象，如商品名称、规格列表、标签列表、配送方式等属性。
     * @return 返回表示操作成功的ServerResponseEntity对象，若保存成功则响应体中包含成功信息，若参数校验不通过则抛出相应异常并返回错误提示信息。
     */
    @PostMapping
    @PreAuthorize("@pms.hasPermission('prod:prod:save')")
    public ServerResponseEntity<String> save(@Valid @RequestBody ProductParam productParam) {
        checkParam(productParam);

        Product product = BeanUtil.copyProperties(productParam, Product.class);
        product.setDeliveryMode(Json.toJsonString(productParam.getDeliveryModeVo()));
        product.setShopId(SecurityUtils.getSysUser().getShopId());
        product.setUpdateTime(new Date());
        if (product.getStatus() == 1) {
            product.setPutawayTime(new Date());
        }
        product.setCreateTime(new Date());
        productService.saveProduct(product);
        return ServerResponseEntity.success();
    }

    /**
     * 修改商品信息的方法，接收一个经过验证（通过@Valid注解）的ProductParam对象作为请求体，代表要更新的商品信息。
     * 首先调用私有方法checkParam对传入的商品参数进行合法性校验，然后通过ProductService的getProductByProdId方法根据传入的商品ID从数据库中获取原商品对象，
     * 判断当前登录用户所属的店铺ID与原商品所属的店铺ID是否一致，若不一致则返回无权限修改的错误提示信息，若有权限则获取原商品的规格列表，
     * 将ProductParam对象的属性复制到新的Product对象中（通过BeanUtil），设置商品的配送方式（将相关对象转换为JSON字符串保存）以及更新时间等信息，
     * 根据原商品状态和传入的新商品状态判断是否需要更新上架时间，接着调用ProductService的updateProduct方法将更新后的商品信息保存到数据库中，
     * 之后遍历与该商品相关的用户ID列表（通过BasketService获取），清除这些用户的购物车商品缓存，同时遍历原商品的规格列表，清除对应规格的缓存，
     * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制，只有具备"prod:prod:update"权限的用户才能访问此方法。
     *
     * @param productParam 包含要更新的商品信息的请求体对象，如商品名称、规格列表、标签列表、配送方式、新的商品状态等属性。
     * @return 返回表示操作成功的ServerResponseEntity对象，若更新成功则响应体中包含成功信息，若参数校验不通过或无权限则返回相应的错误提示信息。
     */
    @PutMapping
    @PreAuthorize("@pms.hasPermission('prod:prod:update')")
    public ServerResponseEntity<String> update(@Valid @RequestBody ProductParam productParam) {
        checkParam(productParam);
        Product dbProduct = productService.getProductByProdId(productParam.getProdId());
        if (!Objects.equals(dbProduct.getShopId(), SecurityUtils.getSysUser().getShopId())) {
            return ServerResponseEntity.showFailMsg("无法修改非本店铺商品信息");
        }

        List<Sku> dbSkus = skuService.listByProdId(dbProduct.getProdId());


        Product product = BeanUtil.copyProperties(productParam, Product.class);
        product.setDeliveryMode(Json.toJsonString(productParam.getDeliveryModeVo()));
        product.setUpdateTime(new Date());

        if (dbProduct.getStatus() == 0 || productParam.getStatus() == 1) {
            product.setPutawayTime(new Date());
        }
        dbProduct.setSkuList(dbSkus);
        productService.updateProduct(product, dbProduct);


        List<String> userIds = basketService.listUserIdByProdId(product.getProdId());

        for (String userId : userIds) {
            basketService.removeShopCartItemsCacheByUserId(userId);
        }
        for (Sku sku : dbSkus) {
            skuService.removeSkuCacheBySkuId(sku.getSkuId(), sku.getProdId());
        }
        return ServerResponseEntity.success();
    }

    /**
     * 删除商品信息的方法，根据传入的商品ID，首先通过ProductService的getProductByProdId方法获取要删除的商品对象，
     * 判断当前登录用户所属的店铺ID与商品所属的店铺ID是否一致，若不一致则抛出无权限异常，若有权限则获取该商品的规格列表，
     * 调用ProductService的removeProductByProdId方法删除商品信息，接着遍历商品规格列表，清除对应规格的缓存，
     * 同时遍历与该商品相关的用户ID列表（通过BasketService获取），清除这些用户的购物车商品缓存，最后返回成功的响应结果给前端。
     * 此方法没有添加权限控制注解，需根据实际业务需求确认是否需要添加相应权限控制（可能是供内部调用等情况）。
     *
     * @param prodId 要删除的商品的唯一标识符。
     * @return 返回表示操作成功的ServerResponseEntity对象，若删除成功则响应体中包含成功信息，若无权限则抛出相应异常并返回错误提示信息。
     */
    public ServerResponseEntity<Void> delete(Long prodId) {
        Product dbProduct = productService.getProductByProdId(prodId);
        if (!Objects.equals(dbProduct.getShopId(), SecurityUtils.getSysUser().getShopId())) {
            throw new YamiShopBindException("无法获取非本店铺商品信息");
        }
        List<Sku> dbSkus = skuService.listByProdId(dbProduct.getProdId());
        // 删除商品，通过ProductService的removeProductByProdId方法根据商品ID删除商品信息。
        productService.removeProductByProdId(prodId);

        for (Sku sku : dbSkus) {
            skuService.removeSkuCacheBySkuId(sku.getSkuId(), sku.getProdId());
        }


        List<String> userIds = basketService.listUserIdByProdId(prodId);

        for (String userId : userIds) {
            basketService.removeShopCartItemsCacheByUserId(userId);
        }

        return ServerResponseEntity.success();
    }

    /**
     * 批量删除商品信息的方法，接收一个包含要删除的商品ID数组的请求体，遍历该数组，对每个商品ID调用delete方法进行删除操作，
     * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制，只有具备"prod:prod:delete"权限的用户才能访问此方法。
     *
     * @param prodIds 包含要删除的商品ID的数组。
     * @return 返回表示操作成功的ServerResponseEntity对象，若批量删除成功则响应体中包含成功信息，若删除过程中出现无权限等问题则抛出相应异常并返回错误提示信息。
     */
    @DeleteMapping
    @PreAuthorize("@pms.hasPermission('prod:prod:delete')")
    public ServerResponseEntity<Void> batchDelete(@RequestBody Long[] prodIds) {
        for (Long prodId : prodIds) {
            delete(prodId);
        }
        return ServerResponseEntity.success();
    }

    /**
     * 更新商品状态的方法，接收商品ID和新的商品状态作为请求参数，创建一个Product对象并设置其ID和状态属性，
     * 若新的商品状态为上架状态（状态值为1）则设置上架时间为当前时间，然后调用ProductService的updateById方法更新商品状态信息，
     * 接着清除该商品的缓存（通过ProductService），再遍历与该商品相关的用户ID列表（通过BasketService获取），清除这些用户的购物车商品缓存，
     * 最后返回成功的响应结果给前端。通过@PreAuthorize注解进行权限控制，只有具备"prod:prod:status"权限的用户才能访问此方法。
     *
     * @param prodId 要更新状态的商品的唯一标识符。
     * @param prodStatus 新的商品状态值，如1表示上架，0表示下架等。
     * @return 返回表示操作成功的ServerResponseEntity对象，若更新成功则响应体中包含成功信息，若操作过程中出现问题则返回相应的错误提示信息。
     */
    @PutMapping("/prodStatus")
    @PreAuthorize("@pms.hasPermission('prod:prod:status')")
    public ServerResponseEntity<Void> shopStatus(@RequestParam Long prodId, @RequestParam Integer prodStatus) {
        Product product = new Product();
        product.setProdId(prodId);
        product.setStatus(prodStatus);
        if (prodStatus == 1) {
            product.setPutawayTime(new Date());
        }
        productService.updateById(product);
        productService.removeProductCacheByProdId(prodId);
        List<String> userIds = basketService.listUserIdByProdId(prodId);

        for (String userId : userIds) {
            basketService.removeShopCartItemsCacheByUserId(userId);
        }
        return ServerResponseEntity.success();
    }

    /**
     * 私有方法，用于对传入的商品参数（ProductParam对象）进行合法性校验，主要校验内容如下：
     * 1. 检查商品的标签列表是否为空，如果为空则抛出异常提示“请选择产品分组”，确保商品关联了相应的分组标签。
     * 2. 检查商品的配送方式，判断是否选择了有效的配送方式（至少有店铺配送或者用户自提其中一种），若未选择则抛出异常提示“请选择配送方式”。
     * 3. 检查商品规格列表中是否至少有一个规格处于启用状态（状态值为