package com.czg.service.market.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson2.JSONArray;
import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO;
import com.czg.account.entity.ShopInfo;
import com.czg.account.entity.ShopUser;
import com.czg.account.service.ShopInfoService;
import com.czg.account.service.ShopUserService;
import com.czg.constant.TableValueConstant;
import com.czg.enums.ShopUserFlowBizEnum;
import com.czg.exception.CzgException;
import com.czg.market.dto.CouponInfoDTO;
import com.czg.market.dto.MkRedemptionConfigDTO;
import com.czg.market.dto.MkRedemptionDTO;
import com.czg.market.dto.MkShopCouponGiftDTO;
import com.czg.market.entity.MkEnableConfig;
import com.czg.market.entity.MkRedemptionCode;
import com.czg.market.entity.MkRedemptionConfig;
import com.czg.market.service.MkRedemptionCodeService;
import com.czg.market.service.MkEnableConfigService;
import com.czg.market.service.MkShopCouponRecordService;
import com.czg.market.vo.MkRedemptionConfigVO;
import com.czg.market.vo.MkRedemptionCodeVO;
import com.czg.market.vo.URedemptionVO;
import com.czg.utils.AssertUtil;
import com.czg.utils.MyQueryWrapper;
import com.czg.utils.PageUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.market.service.MkRedemptionConfigService;
import com.czg.service.market.mapper.MkCouponRedemptionConfigMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 充值优惠券修改 服务层实现。
 *
 * @author zs
 * @since 2025-10-22
 */
@Service
public class MkRedemptionConfigServiceImpl extends ServiceImpl<MkCouponRedemptionConfigMapper, MkRedemptionConfig> implements MkRedemptionConfigService {
    @Resource
    private MkRedemptionCodeService codeService;
    @Resource
    private MkEnableConfigService enableConfigService;
    @Resource
    private MkShopCouponRecordService shopCouponRecordService;

    @DubboReference
    private ShopInfoService shopInfoService;
    @DubboReference
    private ShopUserService shopUserService;
    /**
     * 生成指定活动的兑换码集合
     * @param activityId 活动ID
     * @param count 生成数量（≤1000）
     */
    public static Set<String> generateCodes(long activityId, int count) {
        Set<String> codes = new HashSet<>();

        String actPart = String.format("%03d", activityId % 1000);

        while (codes.size() < count) {
            String patternPart = generatePatternNumber();
            String code = actPart + patternPart;
            codes.add(code);
        }
        return codes;
    }

    /**
     * 生成“有规律”的9位数字串
     * 包含连续或重复数字，提升记忆性
     */
    private static String generatePatternNumber() {
        // 随机选择模式类型
        int mode = RandomUtil.randomInt(1, 4);

        switch (mode) {
            case 1:
                // 连续上升（如 123456789, 456789123）
                int start = RandomUtil.randomInt(0, 5);
                StringBuilder sb1 = new StringBuilder();
                for (int i = 0; i < 9; i++) {
                    sb1.append((start + i) % 10);
                }
                return sb1.toString();

            case 2:
                // 连续下降（如 987654321, 654321987）
                int down = RandomUtil.randomInt(3, 9);
                StringBuilder sb2 = new StringBuilder();
                for (int i = 0; i < 9; i++) {
                    sb2.append((down - i + 10) % 10);
                }
                return sb2.toString();

            case 3:
                // 带重复数字（如 112233445, 778899001）
                String base = RandomUtil.randomNumbers(3);
                String repeat = "%s%s%s%s%s%s".formatted(base.charAt(0), base.charAt(0), base.charAt(1), base.charAt(1), base.charAt(2), base.charAt(2));
                // 补3位随机数
                return (repeat + RandomUtil.randomNumbers(3)).substring(0, 9);

            default:
                return RandomUtil.randomNumbers(9);
        }
    }


    @Override
    public MkRedemptionConfigVO detail(Long mainShopId, Long id) {
        MkRedemptionConfig redemptionConfig = getOne(new QueryWrapper().eq(MkRedemptionConfig::getMainShopId, mainShopId).eq(MkRedemptionConfig::getId, id));
        if (redemptionConfig == null) {
            return null;
        }

        MkRedemptionConfigVO infoList = BeanUtil.copyProperties(redemptionConfig, MkRedemptionConfigVO.class, "couponInfoList");
        if (StrUtil.isNotBlank(redemptionConfig.getCouponInfoList())) {
            infoList.setCouponInfoList(JSONArray.parseArray(redemptionConfig.getCouponInfoList(), CouponInfoDTO.class));
        }

        return infoList;
    }

    public static void main(String[] args) {
        QueryWrapper queryWrapper = new QueryWrapper().eq(MkRedemptionCode::getId, 1);
        queryWrapper.and(and -> {
            and.or(or -> {
                or.gt(MkRedemptionConfig::getStartTime, DateUtil.date());
                or.lt(MkRedemptionConfig::getEndTime, DateUtil.date());

            });

            and.or(or -> {
                or.lt(MkRedemptionConfig::getEndTime, DateUtil.date());
            });
        });

        System.out.println(queryWrapper.toSQL());
    }

    @Override
    public Page<MkRedemptionConfigVO> pageInfo(Long mainShopId, Integer status, boolean isRecharge) {
        QueryWrapper queryWrapper = new QueryWrapper().eq(MkRedemptionConfig::getMainShopId, mainShopId)
                .eq(MkRedemptionConfig::getType, isRecharge ? 0 : 1)
                .orderBy(MkRedemptionConfig::getId, false);
        switch (status) {
            case 0:
                queryWrapper.ge(MkRedemptionConfig::getEndTime, DateUtil.date()).le(MkRedemptionConfig::getStartTime, DateUtil.date());
                break;
            case 1:
                queryWrapper.and(and -> {
                    and.or(or -> {
                       or.lt(MkRedemptionConfig::getEndTime, DateUtil.date());
                    });

                    and.or(or -> {
                        or.gt(MkRedemptionConfig::getStartTime, DateUtil.date());
                    });
                });
                break;
        }
        Page<MkRedemptionConfig> page = page(PageUtil.buildPage(), queryWrapper);
        ArrayList<MkRedemptionConfigVO> configList = new ArrayList<>();
        page.getRecords().forEach(item -> {
            if (item.getStartTime() != null && item.getEndTime() != null) {
                item.setStatus(item.getStartTime().isBefore(DateUtil.date().toLocalDateTime()) && item.getEndTime().isAfter(DateUtil.date().toLocalDateTime()) ? 0 : 1);
            }else {
                item.setStatus(0);
            }

            MkRedemptionConfigVO config = BeanUtil.copyProperties(item, MkRedemptionConfigVO.class, "couponInfoList");
            if (StrUtil.isNotBlank(item.getCouponInfoList())) {
                config.setCouponInfoList(JSONArray.parseArray(item.getCouponInfoList(), CouponInfoDTO.class));
                config.setCouponNum(config.getCouponInfoList().stream().map(CouponInfoDTO::getNum).filter(Objects::nonNull).reduce(0, Integer::sum));
            }
            configList.add(config);
        });

        Page<MkRedemptionConfigVO> page2 = new Page<>();
        BeanUtil.copyProperties(page, page2, "records");
        page2.setRecords(configList);
        return page2;
    }

    @Override
    public boolean add(Long mainShopId, MkRedemptionConfigDTO dto, boolean isRecharge) {
        MkEnableConfig one = enableConfigService.getOne(new QueryWrapper().eq(MkEnableConfig::getMainShopId, mainShopId)
                .eq(MkEnableConfig::getType, isRecharge ? TableValueConstant.EnableConfig.Type.RECHARGE_REDEMPTION.getCode() : TableValueConstant.EnableConfig.Type.COUPON_REDEMPTION.getCode()));
        AssertUtil.isNull(one, "请先配置使用门店再添加兑换码");
        long count = count(new QueryWrapper().eq(MkRedemptionConfig::getName, dto.getName()).eq(MkRedemptionConfig::getMainShopId, mainShopId)
                .eq(MkRedemptionConfig::getType, isRecharge ? 0 : 1));
        AssertUtil.isTrue(count > 0, "名称已存在");
        MkRedemptionConfig config = BeanUtil.copyProperties(dto, MkRedemptionConfig.class, "couponInfoList");
        config.setMainShopId(mainShopId);
        config.setStock(config.getTotal());
        config.setType(isRecharge ? 0 : 1);
        if (dto.getCouponInfoList() != null && !dto.getCouponInfoList().isEmpty()) {
            config.setCouponInfoList(JSONArray.toJSONString(dto.getCouponInfoList()));
        }
        save(config);

        ArrayList<MkRedemptionCode> codeArrayList = new ArrayList<>();

        generateCodes(config.getId(), config.getTotal()).forEach(code -> {
            codeArrayList.add(new MkRedemptionCode().setRedemptionId(config.getId())
                    .setMainShopId(mainShopId).setCode(code));
        });

        return codeService.saveBatch(codeArrayList);
    }

    @Override
    public Boolean edit(Long shopId, MkRedemptionConfigDTO dto, boolean isRecharge) {
        MkRedemptionConfig config = getOne(new QueryWrapper().eq(MkRedemptionConfig::getMainShopId, shopId).eq(MkRedemptionConfig::getId, dto.getId())
                .eq(MkRedemptionConfig::getType, isRecharge ? 0 : 1));
        AssertUtil.isNull(config, "活动不存在");
        if (dto.getStock() != null) {
            if (config.getStock() < dto.getStock()) {
                throw new CzgException("库存仅允许删减");
            }
            int subStock = config.getStock() - dto.getStock();
            List<MkRedemptionCode> subCodeList = codeService.list(new QueryWrapper().eq(MkRedemptionCode::getRedemptionId, config.getId()).eq(MkRedemptionCode::getStatus, 0).limit(subStock));
            subCodeList.forEach(item -> item.setStatus(1));
            codeService.updateBatch(subCodeList);
        }

        BeanUtil.copyProperties(dto, config, "total", "amount", "startTime", "endTime", "couponInfoList");

        if (dto.getCouponInfoList() != null && !dto.getCouponInfoList().isEmpty()) {
            config.setCouponInfoList(JSONArray.toJSONString(dto.getCouponInfoList()));
        }
        return updateById(config);
    }

    @Override
    public Page<MkRedemptionCodeVO> codeList(Long mainShopId, Long id, String code, Integer status, boolean isRecharge) {
        QueryWrapper queryWrapper = new MyQueryWrapper()
                .selectAll(MkRedemptionCode.class)
                .select(ShopUser::getNickName, ShopUser::getPhone)
                .eq(MkRedemptionCode::getMainShopId, mainShopId)
                .eq(MkRedemptionCode::getRedemptionId, id)
                .orderBy(MkRedemptionCode::getId, false);
        queryWrapper.eq(MkRedemptionCode::getStatus, status);
        if (StrUtil.isNotBlank(code)) {
            queryWrapper.like(MkRedemptionCode::getCode, code);
        }
        queryWrapper.leftJoin(ShopUser.class).on(ShopUser::getId, MkRedemptionCode::getShopUserId);
        return codeService.pageAs(PageUtil.buildPage(), queryWrapper, MkRedemptionCodeVO.class);
    }

    @Override
    public void exportCodeList(Long mainShopId, Long redemptionId, String code, Integer status, HttpServletResponse response, HttpServletRequest request, boolean isRecharge) {
        QueryWrapper queryWrapper = new MyQueryWrapper()
                .selectAll(MkRedemptionCode.class)
                .select(ShopUser::getNickName, ShopUser::getPhone)
                .eq(MkRedemptionCode::getMainShopId, mainShopId)
                .eq(MkRedemptionCode::getRedemptionId, redemptionId);
        queryWrapper.eq(MkRedemptionCode::getStatus, status);
        if (StrUtil.isNotBlank(code)) {
            queryWrapper.like(MkRedemptionCode::getCode, code);
        }
        queryWrapper.leftJoin(ShopUser.class).on(ShopUser::getId, MkRedemptionCode::getShopUserId);
        List<MkRedemptionCodeVO> MkCouponRedemptionCodeVOS = codeService.listAs(queryWrapper, MkRedemptionCodeVO.class);

        List<Map<String, Object>> list = MkCouponRedemptionCodeVOS.stream()
                .map(item -> {
                    Map<String, Object> map = new HashMap<>();
                    map.put("code", item.getCode());
                    map.put("redemptionTime", item.getRedemptionTime());
                    map.put("userInfo", item.getShopUserId() == null ? "" : item.getNickName() + " " + item.getPhone());
                    return map;
                })
                .toList();
        // 1. 创建 ExcelWriter
        ExcelWriter writer = ExcelUtil.getWriter(true);

        // 2. 自定义标题别名（可选）

        // 3. 写入数据

        // 4. 设置响应头，告诉浏览器下载文件
        try (writer) {
            writer.addHeaderAlias("code", "兑换码");
            writer.addHeaderAlias("redemptionTime", "兑换时间");
            writer.addHeaderAlias("userInfo", "用户信息");
            writer.write(list, true);
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            String fileName;
            fileName = URLEncoder.encode("兑换码", StandardCharsets.UTF_8);
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
            // 5. 输出到浏览器
            writer.flush(response.getOutputStream(), true);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private MkRedemptionConfigVO checkInfo(MkRedemptionCode redemptionCode) {
        AssertUtil.isNull(redemptionCode, "兑换码不存在");
        AssertUtil.isTrue(redemptionCode.getStatus() == 1, "兑换码已使用");
        Long mainShopId = redemptionCode.getMainShopId();
        enableConfigService.checkEnable(mainShopId, mainShopId, TableValueConstant.EnableConfig.Type.RECHARGE_REDEMPTION, true);


        AssertUtil.isTrue(redemptionCode.getStatus() != 0, "兑换码不存在");

        MkRedemptionConfigVO config = detail(mainShopId, redemptionCode.getRedemptionId());
        AssertUtil.isNull(config, "兑换配置不存在");
        AssertUtil.isTrue(config.getStock() <= 0, "兑换码可兑换库存不足");
        if (config.getStartTime() != null) {
            AssertUtil.isTrue(config.getStartTime().isAfter(DateUtil.date().toLocalDateTime()), "兑换活动未开始");
        }
        if (config.getEndTime() != null) {
            AssertUtil.isTrue(config.getEndTime().isBefore(DateUtil.date().toLocalDateTime()), "兑换活动已结束");
        }
        return config;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void exchange(long userId, MkRedemptionDTO dto) {
        MkRedemptionCode redemptionCode = codeService.getOne(new QueryWrapper().eq(MkRedemptionCode::getCode, dto.getCode()));
        MkRedemptionConfigVO config = checkInfo(redemptionCode);
        ShopUser shopUserInfo = shopUserService.getShopUserInfo(redemptionCode.getMainShopId(), userId);


        if (config.getType() == 0) {
            ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO()
                    .setId(shopUserInfo.getId())
                    .setMoney(config.getAmount())
                    .setType(1)
                    .setBizEnum(ShopUserFlowBizEnum.RECHARGE_REDEMPTION)
                    .setRelationId(redemptionCode.getId());

            shopUserService.updateMoney(shopUserMoneyEditDTO);
        }else {
            if (config.getCouponInfoList() != null && !config.getCouponInfoList().isEmpty()) {
                config.getCouponInfoList().forEach(item -> {
                    MkShopCouponGiftDTO giftDTO = new MkShopCouponGiftDTO().setCouponId(item.getId())
                            .setShopId(redemptionCode.getMainShopId())
                            .setSourceId(redemptionCode.getId())
                            .setShopUserId(shopUserInfo.getId())
                            .setSource("兑换码兑换");
                    shopCouponRecordService.receiveCoupon(giftDTO, item.getNum(), false);

                });

            }
        }

        redemptionCode.setStatus(1);
        redemptionCode.setUserId(shopUserInfo.getUserId());
        redemptionCode.setShopUserId(shopUserInfo.getId());
        redemptionCode.setRedemptionTime(DateUtil.date().toLocalDateTime());
        boolean flag = codeService.updateChain().set(MkRedemptionCode::getStatus, 1)
                .set(MkRedemptionCode::getUserId, shopUserInfo.getUserId())
                .set(MkRedemptionCode::getShopUserId, shopUserInfo.getId())
                .set(MkRedemptionCode::getRedemptionTime, DateUtil.date().toLocalDateTime())
                .eq(MkRedemptionCode::getId, redemptionCode.getId())
                .eq(MkRedemptionCode::getStatus, 0).update();
        AssertUtil.isTrue(!flag, "使用失败");

        boolean update = updateChain().eq(MkRedemptionConfig::getId, config.getId()).setRaw(MkRedemptionConfig::getStock, "stock - 1")
                .where("stock - 1 >= 0").update();
        AssertUtil.isTrue(!update, "库存不足");

    }

    @Override
    public URedemptionVO getInfo(String code) {
        MkRedemptionCode redemptionCode = codeService.getOne(new QueryWrapper().eq(MkRedemptionCode::getCode, code).eq(MkRedemptionCode::getStatus, 0));
        MkRedemptionConfigVO redemptionConfigVO = checkInfo(redemptionCode);
        ShopInfo shopInfo = shopInfoService.getById(redemptionConfigVO.getMainShopId());
        return new URedemptionVO().setShopName(shopInfo.getShopName())
                .setName(redemptionConfigVO.getName()).setCouponInfoList(redemptionConfigVO.getCouponInfoList()).setAmount(redemptionConfigVO.getAmount());
    }
}
