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

package com.yami.shop.api.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.app.dto.UserAddrDto;
import com.yami.shop.bean.app.param.AddrParam;
import com.yami.shop.bean.model.UserAddr;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.response.ServerResponseEntity;
import com.yami.shop.security.api.util.SecurityUtils;
import com.yami.shop.service.UserAddrService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import cn.hutool.core.bean.BeanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

/**
 * 地址相关接口的控制器类，用于处理与用户地址相关的各种操作请求，例如获取地址列表、新增地址、修改地址、删除地址、设置默认地址以及获取单个地址信息等。
 *
 * @author lanhai
 */
@RestController
@RequestMapping("/p/address")
@Tag(name = "地址接口")
@AllArgsConstructor
public class AddrController {

    // 自动注入用户地址服务层接口，通过该接口调用具体的业务逻辑方法来处理用户地址相关业务
    @Autowired
    private UserAddrService userAddrService;

    /**
     * 获取用户地址列表的方法，用于选择订单配送地址时展示所有可用的用户地址。
     * 通过查询数据库，获取当前用户的所有地址信息，并按照是否为常用地址以及更新时间进行降序排序，最后将查询到的地址信息转换为对应的 DTO 类型返回给前端。
     *
     * @return 包含用户地址信息列表的 ServerResponseEntity，其中地址信息以 UserAddrDto 类型封装，方便前端展示和使用。
     */
    @GetMapping("/list")
    @Operation(summary = "用户地址列表", description = "获取用户的所有地址信息")
    public ServerResponseEntity<List<UserAddrDto>> dvyList() {
        // 获取当前用户的 ID，用于筛选属于该用户的地址记录
        String userId = SecurityUtils.getUser().getUserId();
        // 使用 MyBatis Plus 的 LambdaQueryWrapper 构建查询条件，查询当前用户的所有地址信息，并按照常用地址和更新时间降序排序
        List<UserAddr> userAddrs = userAddrService.list(new LambdaQueryWrapper<UserAddr>()
               .eq(UserAddr::getUserId, userId)
               .orderByDesc(UserAddr::getCommonAddr)
               .orderByDesc(UserAddr::getUpdateTime));
        // 使用 Hutool 的 BeanUtil 将查询到的 UserAddr 类型的地址列表转换为 UserAddrDto 类型列表，方便返回给前端展示
        return ServerResponseEntity.success(BeanUtil.copyToList(userAddrs, UserAddrDto.class));
    }

    /**
     * 新增用户地址的方法，接收包含地址信息的 AddrParam 对象，进行一系列验证和设置后将新地址保存到数据库中，同时处理默认地址缓存相关逻辑。
     * 如果传入的地址 ID 不为空且不为 0，则认为该地址已存在，返回相应的错误提示信息；若用户当前没有地址记录，则新增的地址设置为默认地址；否则设置为非默认地址，
     * 最后保存地址信息到数据库，并根据是否为默认地址决定是否清除默认地址缓存，成功保存后返回成功提示信息给前端。
     *
     * @param addrParam 包含要新增的用户地址信息的参数对象，通过请求体传入，且经过了参数验证（@Valid 注解）
     * @return 包含操作结果提示信息的 ServerResponseEntity，成功则返回 "添加地址成功"，地址 ID 不符合要求则返回相应错误提示。
     */
    @PostMapping("/addAddr")
    @Operation(summary = "新增用户地址", description = "新增用户地址")
    public ServerResponseEntity<String> addAddr(@Valid @RequestBody AddrParam addrParam) {
        // 获取当前用户的 ID，用于关联新地址到该用户
        String userId = SecurityUtils.getUser().getUserId();

        // 如果传入的地址 ID 不为空且不等于 0，说明该地址可能已存在，返回相应的错误提示信息
        if (addrParam.getAddrId()!= null && addrParam.getAddrId()!= 0) {
            return ServerResponseEntity.showFailMsg("该地址已存在");
        }

        // 统计当前用户已有的地址数量，用于判断是否需要将新地址设置为默认地址
        long addrCount = userAddrService.count(new LambdaQueryWrapper<UserAddr>().eq(UserAddr::getUserId, userId));
        // 使用 Hutool 的 BeanUtil 将 AddrParam 类型的参数对象转换为 UserAddr 类型，方便保存到数据库
        UserAddr userAddr = BeanUtil.copyProperties(addrParam, UserAddr.class);

        // 如果当前用户没有地址记录，则将新地址设置为默认地址（常用地址，commonAddr 设为 1）
        if (addrCount == 0) {
            userAddr.setCommonAddr(1);
        } else {
            // 否则设置为非默认地址（commonAddr 设为 0）
            userAddr.setCommonAddr(0);
        }

        // 设置用户 ID，明确该地址所属的用户
        userAddr.setUserId(userId);
        // 设置地址状态为有效（这里假设 1 表示有效状态）
        userAddr.setStatus(1);
        // 设置地址的创建时间为当前时间
        userAddr.setCreateTime(new Date());
        // 设置地址的更新时间为当前时间
        userAddr.setUpdateTime(new Date());

        // 调用用户地址服务层的保存方法，将新地址信息保存到数据库中
        userAddrService.save(userAddr);

        // 如果新地址被设置为默认地址（commonAddr 为 1），则清除默认地址缓存，确保缓存数据的一致性
        if (userAddr.getCommonAddr() == 1) {
            userAddrService.removeUserAddrByUserId(0L, userId);
        }

        return ServerResponseEntity.success("添加地址成功");
    }

    /**
     * 修改用户地址的方法，接收包含更新后地址信息的 AddrParam 对象，先验证地址是否存在，然后更新地址信息到数据库中，
     * 同时清除当前地址和默认地址的缓存，确保数据的准确性和缓存的一致性，最后返回修改成功的提示信息给前端。
     *
     * @param addrParam 包含要修改的用户地址信息的参数对象，通过请求体传入，且经过了参数验证（@Valid 注解）
     * @return 包含操作结果提示信息的 ServerResponseEntity，成功则返回 "修改地址成功"，若地址不存在则返回相应错误提示。
     */
    @PutMapping("/updateAddr")
    @Operation(summary = "修改订单用户地址", description = "修改用户地址")
    public ServerResponseEntity<String> updateAddr(@Valid @RequestBody AddrParam addrParam) {
        // 获取当前用户的 ID，用于验证地址是否属于该用户以及后续更新操作
        String userId = SecurityUtils.getUser().getUserId();

        // 根据传入的地址 ID 和用户 ID，从数据库中查询对应的用户地址记录，若不存在则返回相应的错误提示信息
        UserAddr dbUserAddr = userAddrService.getUserAddrByUserId(addrParam.getAddrId(), userId);
        if (dbUserAddr == null) {
            return ServerResponseEntity.showFailMsg("该地址已被删除");
        }

        // 使用 Hutool 的 BeanUtil 将 AddrParam 类型的参数对象转换为 UserAddr 类型，准备更新到数据库
        UserAddr userAddr = BeanUtil.copyProperties(addrParam, UserAddr.class);
        // 设置用户 ID，确保更新的是正确用户的地址记录
        userAddr.setUserId(userId);
        // 设置地址的更新时间为当前时间
        userAddr.setUpdateTime(new Date());

        // 调用用户地址服务层的更新方法，将更新后的地址信息保存到数据库中
        userAddrService.updateById(userAddr);

        // 清除当前地址的缓存，保证缓存数据与数据库最新数据一致
        userAddrService.removeUserAddrByUserId(addrParam.getAddrId(), userId);
        // 清除默认地址的缓存，确保默认地址相关数据的准确性
        userAddrService.removeUserAddrByUserId(0L, userId);

        return ServerResponseEntity.success("修改地址成功");
    }

    /**
     * 删除用户地址的方法，根据传入的地址 ID，先验证地址是否存在以及是否为默认地址（默认地址不允许删除），
     * 若验证通过则从数据库中删除该地址记录，并清除对应地址的缓存，最后返回删除成功的提示信息给前端。
     *
     * @param addrId 要删除的用户地址的 ID，通过路径变量传入
     * @return 包含操作结果提示信息的 ServerResponseEntity，成功则返回 "删除地址成功"，若地址不存在或为默认地址则返回相应错误提示。
     */
    @DeleteMapping("/deleteAddr/{addrId}")
    @Operation(summary = "删除订单用户地址", description = "根据地址id，删除用户地址")
    @Parameter(name = "addrId", description = "地址ID", required = true)
    public ServerResponseEntity<String> deleteDvy(@PathVariable("addrId") Long addrId) {
        // 获取当前用户的 ID，用于验证地址是否属于该用户以及后续删除操作
        String userId = SecurityUtils.getUser().getUserId();
        // 根据传入的地址 ID 和用户 ID，从数据库中查询对应的用户地址记录，若不存在则返回相应的错误提示信息
        UserAddr userAddr = userAddrService.getUserAddrByUserId(addrId, userId);
        if (userAddr == null) {
            return ServerResponseEntity.showFailMsg("该地址已被删除");
        }

        // 如果该地址是默认地址（commonAddr 为 1），则不允许删除，返回相应的错误提示信息
        if (userAddr.getCommonAddr() == 1) {
            return ServerResponseEntity.showFailMsg("默认地址无法删除");
        }

        // 调用用户地址服务层的删除方法，从数据库中删除该地址记录
        userAddrService.removeById(addrId);
        // 清除对应地址的缓存，保证缓存数据与数据库数据的一致性
        userAddrService.removeUserAddrByUserId(addrId, userId);

        return ServerResponseEntity.success("删除地址成功");
    }

    /**
     * 设置默认地址的方法，根据传入的地址 ID，调用服务层方法将该地址设置为当前用户的默认地址，
     * 同时清除默认地址和当前地址的缓存，确保缓存数据与数据库最新状态一致，最后返回修改成功的提示信息给前端。
     *
     * @param addrId 要设置为默认地址的用户地址的 ID，通过路径变量传入
     * @return 包含操作结果提示信息的 ServerResponseEntity，成功则返回 "修改地址成功"。
     */
    @PutMapping("/defaultAddr/{addrId}")
    @Operation(summary = "设置默认地址", description = "根据地址id，设置默认地址")
    public ServerResponseEntity<String> defaultAddr(@PathVariable("addrId") Long addrId) {
        // 获取当前用户的 ID，用于关联默认地址到该用户
        String userId = SecurityUtils.getUser().getUserId();

        // 调用用户地址服务层的方法，将指定地址 ID 的地址设置为当前用户的默认地址
        userAddrService.updateDefaultUserAddr(addrId, userId);

        // 清除默认地址的缓存，保证缓存数据与数据库最新数据一致
        userAddrService.removeUserAddrByUserId(0L, userId);
        // 清除当前设置为默认地址的地址缓存，确保相关数据准确性
        userAddrService.removeUserAddrByUserId(addrId, userId);

        return ServerResponseEntity.success("修改地址成功");
    }

    /**
     * 获取单个用户地址详细信息的方法，根据传入的地址 ID，先验证地址是否存在，若存在则将对应的地址信息转换为 UserAddrDto 类型返回给前端，
     * 若地址不存在则抛出相应的异常（由全局异常处理机制处理，这里假设会返回合适的错误提示给前端）。
     *
     * @param addrId 要获取详细信息的用户地址的 ID，通过路径变量传入
     * @return 包含单个用户地址详细信息的 ServerResponseEntity，以 UserAddrDto 类型封装地址信息，若地址不存在则抛出异常。
     */
    @GetMapping("/addrInfo/{addrId}")
    @Operation(summary = "获取地址信息", description = "根据地址id，获取地址信息")
    @Parameter(name = "addrId", description = "地址ID", required = true)
    public ServerResponseEntity<UserAddrDto> addrInfo(@PathVariable("addrId") Long addrId) {
        // 获取当前用户的 ID，用于验证地址是否属于该用户以及查询操作
        String userId = SecurityUtils.getUser().getUserId();
        // 根据传入的地址 ID 和用户 ID，从数据库中查询对应的用户地址记录，若不存在则抛出相应的异常
        UserAddr userAddr = userAddrService.getUserAddrByUserId(addrId, userId);
        if (userAddr == null) {
            throw new YamiShopBindException("该地址已被删除");
        }

        // 使用 Hutool 的 BeanUtil 将查询到的 UserAddr 类型的地址信息转换为 UserAddrDto 类型，方便返回给前端展示
        return ServerResponseEntity.success(BeanUtil.copyProperties(userAddr, UserAddrDto.class));
    }
}