package cn.ysk.cashier.service.impl.app;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import cn.ysk.cashier.cons.RedisConstant;
import cn.ysk.cashier.dto.calltable.*;
import cn.ysk.cashier.exception.BadRequestException;
import cn.ysk.cashier.mybatis.entity.TbCallConfig;
import cn.ysk.cashier.mybatis.entity.TbCallQueue;
import cn.ysk.cashier.mybatis.entity.TbCallTable;
import cn.ysk.cashier.mybatis.mapper.TbCallQueueMapper;
import cn.ysk.cashier.mybatis.mapper.TbShopUserMapper;
import cn.ysk.cashier.mybatis.service.TbCallConfigService;
import cn.ysk.cashier.mybatis.service.TbCallQueueService;
import cn.ysk.cashier.mybatis.service.TbCallTableService;
import cn.ysk.cashier.pojo.shop.TbShopInfo;
import cn.ysk.cashier.pojo.shop.TbShopUser;
import cn.ysk.cashier.repository.shop.TbShopInfoRepository;
import cn.ysk.cashier.service.app.TbCallService;
import cn.ysk.cashier.utils.RabbitMsgUtils;
import cn.ysk.cashier.utils.Utils;
import cn.ysk.cashier.utils.WxMiniUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
public class TbCallServiceImpl implements TbCallService {

    @Value("${wx.mini.page.call}")
    private String callPageUrl;

    private final TbCallTableService callTableService;
    private final TbCallQueueService callQueueService;
    private final TbShopUserMapper shopUserMapper;
    private final TbShopInfoRepository shopInfoRepository;
    private final StringRedisTemplate redisTemplate;
    private final TbCallQueueMapper tbCallQueueMapper;
    private final WxMiniUtils wxMiniUtils;
    private final TbCallConfigService tbCallConfigService;
    private final RabbitMsgUtils rabbitMsgUtils;

    public TbCallServiceImpl(TbCallTableService callTableService, TbCallQueueService callQueueService, TbShopUserMapper shopUserMapper, TbShopInfoRepository shopInfoRepository, StringRedisTemplate redisTemplate, TbCallQueueMapper tbCallQueueMapper, WxMiniUtils wxMiniUtils, TbCallConfigService tbCallConfigService, RabbitMsgUtils rabbitMsgUtils) {
        this.callTableService = callTableService;
        this.callQueueService = callQueueService;
        this.shopUserMapper = shopUserMapper;
        this.shopInfoRepository = shopInfoRepository;
        this.redisTemplate = redisTemplate;
        this.tbCallQueueMapper = tbCallQueueMapper;
        this.wxMiniUtils = wxMiniUtils;
        this.tbCallConfigService = tbCallConfigService;
        this.rabbitMsgUtils = rabbitMsgUtils;
    }

    @Override
    public Object add(CallTableDTO addCallTableDTO) {
        Long count = callTableService.lambdaQuery()
                .eq(TbCallTable::getShopId, addCallTableDTO.getShopId())
                .and(q -> q.eq(TbCallTable::getName, addCallTableDTO.getName())
                        .or()
                        .eq(TbCallTable::getPrefix, addCallTableDTO.getPrefix())).count();
        if (count > 0) {
            throw new BadRequestException("名称或前缀已存在");
        }

        TbCallTable callTable = BeanUtil.copyProperties(addCallTableDTO, TbCallTable.class);
        callTable.setCreateTime(DateUtil.date().toInstant());
        return callTableService.save(callTable);
    }

    @Override
    public Object update(UpdateCallTableDTO callTableDTO) {
        TbCallTable callTable = callTableService.lambdaQuery()
                .eq(TbCallTable::getShopId, callTableDTO.getShopId())
                .eq(TbCallTable::getId, callTableDTO.getCallTableId()).one();

        if (callTable == null) {
            throw new BadRequestException("桌型不存在");
        }
        TbCallTable newInfo = BeanUtil.copyProperties(callTableDTO, TbCallTable.class);
        newInfo.setId(callTable.getId());
        newInfo.setUpdateTime(DateUtil.date().toInstant());

        return callTableService.updateById(newInfo);
    }

    @Override
    public Object delete(BaseCallTableDTO baseCallTableDTO) {
        return callTableService.remove(new LambdaQueryWrapper<TbCallTable>()
                .eq(TbCallTable::getId, baseCallTableDTO.getCallTableId())
                .eq(TbCallTable::getShopId, baseCallTableDTO.getShopId()));
    }

    private String getCallNumber(Integer shopId, TbCallTable callTable) {
        return Utils.runFunAndCheckKey(() -> {
            String callNumKey = RedisConstant.getTableCallNumKey(shopId, callTable.getId());
            String value = redisTemplate.opsForValue().get(callNumKey);
            AtomicReference<String> newVal = new AtomicReference<>("");
            // 初始化
            if (StrUtil.isBlank(value)) {
                Boolean setFlag = Utils.runFunAndRetry(() -> redisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
                        r -> newVal.set(redisTemplate.opsForValue().get(callNumKey)));

                if (setFlag) {
                    return callTable.getPrefix() + callTable.getStart();
                }else if (StrUtil.isNotBlank(newVal.get())){
                    value = String.valueOf((Integer.parseInt(newVal.get()) + 1));
                    redisTemplate.opsForValue().set(callNumKey, value);
                    return callTable.getPrefix() + value;
                }else {
                    throw new BadRequestException("生成排队号码失败");
                }

            }else {
                value = String.valueOf((Integer.parseInt(value) + 1));
                redisTemplate.opsForValue().set(callNumKey, value);
                return callTable.getPrefix() + value;
            }
        }, redisTemplate, RedisConstant.getLockKey("UPDATE_TABLE", shopId, callTable.getId()));

    }

    @Override
    public Object takeNumber(TakeNumberDTO takeNumberDTO) {
        TbShopInfo shopInfo = shopInfoRepository.findById(takeNumberDTO.getShopId()).orElse(null);
        if (shopInfo == null) {
            throw new BadRequestException("店铺信息不存在");
        }

        TbCallTable callTable = callTableService.lambdaQuery()
                .eq(TbCallTable::getShopId, takeNumberDTO.getShopId())
                .eq(TbCallTable::getId, takeNumberDTO.getCallTableId()).one();
        if (callTable == null) {
            throw new BadRequestException("桌型不存在");
        }

        // 查询当前

        // 拿取系统内部用户信息
        TbCallQueue callQueue;
        if (takeNumberDTO.getUserId() != null) {
            TbShopUser shopUser = shopUserMapper.selectOne(new LambdaQueryWrapper<TbShopUser>()
                    .eq(TbShopUser::getStatus, 1)
                    .eq(TbShopUser::getShopId, takeNumberDTO.getShopId())
                    .eq(TbShopUser::getId, takeNumberDTO.getUserId()));
            if (shopUser == null) {
                throw new BadRequestException("用户不存在");
            }

            callQueue = callQueueService.lambdaQuery()
                    .eq(TbCallQueue::getUserId, shopUser.getId())
                    .eq(TbCallQueue::getShopId, takeNumberDTO.getShopId())
                    .in(TbCallQueue::getState, 0, 1)
                    .eq(TbCallQueue::getCreateDay, DateUtil.today())
                    .ne(TbCallQueue::getIsPostpone, 2)
                    .eq(TbCallQueue::getCallTableId, takeNumberDTO.getCallTableId()).one();
            if (callQueue != null) {
                throw new BadRequestException("当前用户已取号");
            }

            callQueue = BeanUtil.copyProperties(takeNumberDTO, TbCallQueue.class);
            callQueue.setPhone(StrUtil.isBlank(takeNumberDTO.getPhone()) ? shopUser.getTelephone() : takeNumberDTO.getPhone());

//            callQueue.setOpenId(shopUser.getMiniOpenId());

        }else {
//            if (StrUtil.isBlank(takeNumberDTO.getPhone()) || StrUtil.isBlank(takeNumberDTO.getOpenId())) {
//                throw new BadRequestException("手机号或openId不能为空");
//            }

            callQueue = callQueueService.lambdaQuery()
                    .eq(TbCallQueue::getPhone, takeNumberDTO.getPhone())
                    .eq(TbCallQueue::getShopId, takeNumberDTO.getShopId())
                    .eq(TbCallQueue::getCreateDay, DateUtil.today())
                    .in(TbCallQueue::getState, 0, 1)
                    .ne(TbCallQueue::getIsPostpone, 2)
                    .eq(TbCallQueue::getCallTableId, takeNumberDTO.getCallTableId()).one();
            if (callQueue != null) {
                throw new BadRequestException("当前用户已取号");
            }

            callQueue = BeanUtil.copyProperties(takeNumberDTO, TbCallQueue.class);
            callQueue.setPhone(takeNumberDTO.getPhone());
//            callQueue.setOpenId(takeNumberDTO.getOpenId());
            callQueue.setSubState(0);
        }

        callQueue.setCreateDay(DateUtil.today());
        callQueue.setCallNum(getCallNumber(takeNumberDTO.getShopId(), callTable));
        callQueue.setCreateTime(DateUtil.date());
        callQueue.setShopId(shopInfo.getId());
        callQueue.setShopName(shopInfo.getShopName());

        callQueueService.save(callQueue);

        // 打印排号票信息
        rabbitMsgUtils.printCallNumTicket(callQueue.getId(), callQueue.getShopId());

        HashMap<String, Object> data = new HashMap<>();
        data.put("tableName", callTable.getName());
        data.put("tableNote", callTable.getNote());
        data.put("callNum", callQueue.getCallNum());
        return data;
    }

    @Override
    public Object call(CallQueueDTO callQueueDTO) {
        TbCallQueue callQueue = callQueueService.lambdaQuery()
                .notIn(TbCallQueue::getState, -1, 2)
                .eq(TbCallQueue::getCreateDay, DateUtil.today())
                .eq(TbCallQueue::getId, callQueueDTO.getCallQueueId())
                .eq(TbCallQueue::getShopId, callQueueDTO.getShopId())
                .one();

        if (callQueue == null) {
            throw new BadRequestException("叫号用户不存在");
        }

        callQueue.setState((byte) 1);
        callQueue.setCallCount(callQueue.getCallCount() + 1);
        callQueue.setCallTime(DateUtil.date());
        callQueueService.updateById(callQueue);

        if (callQueue.getSubState().equals(0)) {
            return new HashMap<String, Object>(){{
                put("state", "-1");
            }};
        }

        // 发送模板消息通知用户

        TbShopInfo shopInfo = shopInfoRepository.findById(callQueue.getShopId()).orElse(null);
        if (shopInfo == null) {
            throw new BadRequestException("店铺信息不存在");
        }

        List<TbCallQueue> current = callQueueService.lambdaQuery()
                .eq(TbCallQueue::getCallTableId, callQueue.getCallTableId())
                .eq(TbCallQueue::getCreateDay, DateUtil.today())
                .and(r -> {
                    r.eq(TbCallQueue::getState, 1)
                            .or()
                            .eq(TbCallQueue::getState, 0);
                })
                .orderByAsc(TbCallQueue::getCreateTime)
                .page(new Page<>(1, 1)).getRecords();


        if (StrUtil.isBlank(callQueue.getOpenId())) {
            return new HashMap<String, Object>(){{
                put("state", "-1");
            }};
        }

        wxMiniUtils.sendCurrentOrNearCallMsg(shopInfo.getShopName(), getStrByState(Integer.valueOf(callQueue.getState())),
                callQueue.getCallNum(), current.isEmpty() ? "" : current.get(0).getCallNum(), "排号信息", callQueue.getOpenId(), false);

        TbCallConfig config = getConfig(callQueueDTO.getShopId());
        // 临近用户提醒
        List<TbCallQueue> nearList = callQueueService.lambdaQuery()
                .eq(TbCallQueue::getCallTableId, callQueue.getCallTableId())
                .eq(TbCallQueue::getCreateDay, DateUtil.today())
                .gt(TbCallQueue::getId, current.get(0).getId())
                .page(new Page<>(config.getNearNum(), 1)).getRecords();
        if (!nearList.isEmpty()) {
            TbCallQueue nearQueue = nearList.get(0);
            wxMiniUtils.sendCurrentOrNearCallMsg(shopInfo.getShopName(), getStrByState(Integer.valueOf(nearQueue.getState())),
                    nearQueue.getCallNum(), current.isEmpty() ? "" : current.get(0).getCallNum(), "排号信息", nearQueue.getOpenId(), true);
        }
        return new HashMap<String, Object>(){{
            put("state", "1");
        }};
    }

    private String getStrByState(Integer state) {
        String msg = "";
        switch (state) {
            case -1:
                msg = "已取消";
                break;
            case 0:
                msg = "排队中";
                break;
            case 1:
                msg = "已到号";
                break;
            case 3:
                msg = "已过号";
        }

        return msg;
    }

    @Override
    public Object updateInfo(UpdateCallQueueDTO updateCallQueueDTO) {
        TbCallQueue callQueue = callQueueService.lambdaQuery()
                .eq(TbCallQueue::getShopId, updateCallQueueDTO.getShopId())
                .eq(TbCallQueue::getCreateDay, DateUtil.today())
                .eq(TbCallQueue::getId, updateCallQueueDTO.getCallQueueId()).one();
        if (callQueue == null) {
            throw new BadRequestException("叫号用户不存在");
        }

        switch (updateCallQueueDTO.getState()) {
            case -1:
                callQueue.setCancelTime(DateUtil.date());
                break;
            case 0:
                callQueue.setState((byte) 0);
                break;
            case 1:
                if (callQueue.getSubState().equals(0)) {
                    throw new BadRequestException("当前用户未订阅微信提醒");
                }
                callQueue.setState((byte) 1);
                callQueue.setCallCount(callQueue.getCallCount() + 1);
                callQueue.setCallTime(DateUtil.date());
                break;
            case 2:
                callQueue.setConfirmTime(DateUtil.date());
                break;
            case 3:
                callQueue.setPassTime(DateUtil.date());
                // 已经顺延
                callQueue.setIsPostpone(callQueue.getIsPostpone() == null ? 1 : callQueue.getIsPostpone() == 0 ? 1 : 2);
                TbShopInfo shopInfo = shopInfoRepository.findById(callQueue.getShopId()).orElse(null);
                if (shopInfo == null) {
                    throw new BadRequestException("店铺信息不存在");
                }

                if(StrUtil.isBlank(callQueue.getOpenId()) && callQueue.getSubState() != 1) {
                    break;
                }

                TbCallTable callTable = callTableService.getById(callQueue.getCallTableId());
                Byte isPostpone = callTable.getIsPostpone();
                Integer postponeNum = callTable.getPostponeNum();

                // 判断是否需要顺延, 暂时注释
                if (false && callQueue.getIsPostpone() == 0 && isPostpone != null && isPostpone == 1 && postponeNum != null && postponeNum > 0) {
                    // 查询当前桌以及顺延桌数
                    List<TbCallQueue> current = callQueueService.lambdaQuery()
                            .eq(TbCallQueue::getCallTableId, callQueue.getCallTableId())
                            .eq(TbCallQueue::getCreateDay, DateUtil.today())
                            .eq(TbCallQueue::getShopId, callTable.getShopId())
                            .ge(TbCallQueue::getId, callQueue.getId())
                            .orderByAsc(TbCallQueue::getCreateTime)
                            .page(new Page<>(1, postponeNum + 1)) // 获取当前桌和顺延的桌数
                            .getRecords();

                    // 确保有足够的桌可以顺延
                    if (current.size() > 1) {
                        // 获取当前桌以及顺延桌
                        TbCallQueue currentTable = BeanUtil.copyProperties(current.get(0), TbCallQueue.class);
                        // 顺延替换信息，将每一张顺延桌向前移动
                        for (int i = 0; i < current.size() - 1; i++) {
                            exchangeCallQueueInfo(current.get(i), current.get(i + 1));  // 当前桌替换为顺延桌
                        }

                        exchangeCallQueueInfo(current.get(current.size() - 1), currentTable);
                        callQueue = current.get(current.size() - 1);

                        // 更新数据库中的桌号信息
                        callQueueService.updateBatchById(current);
                    }
                }

                List<TbCallQueue> current = callQueueService.lambdaQuery()
                        .eq(TbCallQueue::getCallTableId, callQueue.getCallTableId())
                        .eq(TbCallQueue::getCreateDay, DateUtil.today())
                        .and(r -> {
                            r.eq(TbCallQueue::getState, 1)
                                    .or()
                                    .eq(TbCallQueue::getState, 0);
                        })
                        .orderByAsc(TbCallQueue::getCreateTime)
                        .page(new Page<>(1, 1)).getRecords();
                wxMiniUtils.sendPassCallMsg(shopInfo.getShopName(), getStrByState(Integer.valueOf(updateCallQueueDTO.getState())),
                        callQueue.getCallNum(), current.isEmpty() ? "" : current.get(0).getCallNum(), "即将过号", callQueue.getOpenId());
                break;
            default:
                throw new BadRequestException("错误类型");

        }

        callQueue.setState(updateCallQueueDTO.getState());

        return callQueueService.updateById(callQueue);
    }

    private void exchangeCallQueueInfo(TbCallQueue setCallQueue, TbCallQueue copyCallQueue) {
        setCallQueue.setOpenId(copyCallQueue.getOpenId());
        setCallQueue.setState(copyCallQueue.getState());
        setCallQueue.setSubState(copyCallQueue.getSubState());
        setCallQueue.setCreateTime(copyCallQueue.getCreateTime());
        setCallQueue.setName(copyCallQueue.getName());
        setCallQueue.setNote(copyCallQueue.getNote());
        setCallQueue.setCallNum(copyCallQueue.getCallNum());
        setCallQueue.setCallTime(copyCallQueue.getCallTime());
        setCallQueue.setCallCount(copyCallQueue.getCallCount());
        setCallQueue.setPassTime(copyCallQueue.getPassTime());
        setCallQueue.setCancelTime(copyCallQueue.getCancelTime());
        setCallQueue.setUserId(copyCallQueue.getUserId());
        setCallQueue.setConfirmTime(copyCallQueue.getConfirmTime());

    }

    @Override
    public Object get(Integer page, Integer size, Integer shopId, Integer callTableId, Integer state) {
        LambdaQueryWrapper<TbCallTable> query = new LambdaQueryWrapper<TbCallTable>()
                .eq(TbCallTable::getShopId, shopId)
                .eq(TbCallTable::getState, 1);

        if (callTableId != null) {
            query.eq(TbCallTable::getId, callTableId);
        }

        if (state != null) {
            query.eq(TbCallTable::getState, state);
        }
        Page<TbCallTable> pageInfo = callTableService.page(new Page<>(page, size), query);
        ArrayList<Map<String, Object>> info = new ArrayList<>();
        Long totalCount = 0L;
        for (TbCallTable item : pageInfo.getRecords()) {
            LambdaQueryChainWrapper<TbCallQueue> q = callQueueService.lambdaQuery()
                    .eq(TbCallQueue::getCallTableId, item.getId())
                    .eq(TbCallQueue::getShopId, shopId)
                    .eq(TbCallQueue::getCreateDay, DateUtil.today())
                    .in(TbCallQueue::getState, 0, 1);
            Long count = q.count();

            totalCount += count;

            Map<String, Object> map = BeanUtil.beanToMap(item, false, false);
            map.put("totalCount", count);
            map.put("isPostpone", item.getIsPostpone() == 1);
            info.add(map);
        }

        Map<String, Object> toMap = BeanUtil.beanToMap(pageInfo);
        toMap.put("records", info);
        toMap.put("totalCount", totalCount);
        return toMap;
    }

    @Override
    public Object takeNumberCode(Integer shopId, Integer callTableId) {
        // 创建二维码配置对象，设置宽度和高度为400
        QrConfig config = new QrConfig(400, 400);

        // 使用字节数组输出流来存储二维码图片
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        // 生成二维码图片，输出到字节数组输出流
        QrCodeUtil.generate(StrUtil.format(callPageUrl, shopId, ""), config, "png", outputStream);

        // 将图片转换为 Base64 字符串
        String base64 = Base64.getEncoder().encodeToString(outputStream.toByteArray());

        // 返回Base64格式的图片字符串
        return "data:image/png;base64," + base64;
    }

    @Override
    public Object getQueue(Integer shopId, Integer callTableId, Integer state, Integer page, Integer size) {
        List<Integer> tableIds;
        if (callTableId != null) {
            tableIds = Collections.singletonList(callTableId);
        }else {
            List<TbCallTable> list = callTableService.lambdaQuery()
                    .eq(TbCallTable::getShopId, shopId)
                    .eq(TbCallTable::getState, 1).list();
            if (list.isEmpty()) {
                return new Page<>();
            }
            tableIds = list.stream()
                    .map(TbCallTable::getId)
                    .collect(Collectors.toList());
        }

        LambdaQueryChainWrapper<TbCallQueue> query = callQueueService.lambdaQuery()
                .eq(TbCallQueue::getShopId, shopId)
                .eq(TbCallQueue::getCreateDay, DateUtil.today())
                .in(TbCallQueue::getCallTableId, tableIds);
        state = null;
        if (state != null) {
            query.eq(TbCallQueue::getState, state);
        }else {
            query.in(TbCallQueue::getState, 0, 1);
        }
        Page<TbCallQueue> pageInfo = query
                .orderByAsc(TbCallQueue::getCreateTime)
                .orderByDesc(TbCallQueue::getState)
                .page(new Page<>(page, size));

        List<TbCallQueue> list1 = pageInfo.getRecords();

        // 创建返回的结果集
        List<Map<String, Object>> resultList = new ArrayList<>();

        // 遍历每一个叫号中的记录，计算前面状态为"0" (排队中) 的人数
        for (TbCallQueue calling : list1) {
            // 计算前面等待的人数 (状态为"0"且在叫号记录创建时间之前的)
            long waitingCount = 0;
            if (calling.getState() == 0) {
                waitingCount = list1.stream()
                        .filter(item -> item.getState() == 0 || item.getState() == 1)  // 过滤出状态为"排队中"的
                        .filter(item -> item.getCreateTime().compareTo(calling.getCreateTime()) < 0 )  // 时间在当前叫号之前
                        .count();
            }

            // 创建一个Map来保存叫号中的记录及其前面排队的人数
            Map<String, Object> map = BeanUtil.beanToMap(calling, false, false);
            map.put("waitingCount", waitingCount);
            // 将该map加入结果集
            resultList.add(map);
        }

        Map<String, Object> toMap = BeanUtil.beanToMap(pageInfo, false, false);
        toMap.put("records", resultList);

        // 返回结果列表
        return toMap;
    }

    @Override
    public Object getCallRecord(Integer shopId, Integer callTableId, Integer page, Integer size) {
        return tbCallQueueMapper.selectCallRecord(shopId, callTableId, new Page<>(page, size));
    }

    @Override
    public TbCallConfig getConfig(Integer shopId) {
        TbCallConfig config = tbCallConfigService.lambdaQuery().eq(TbCallConfig::getShopId, shopId).one();
        if (config == null) {
            config = new TbCallConfig();
            config.setShopId(shopId);
            config.setCreateTime(DateUtil.date().toInstant());
            tbCallConfigService.save(config);
            config = tbCallConfigService.lambdaQuery().eq(TbCallConfig::getShopId, shopId).one();
        }

        return config;
    }

    @Override
    public Object updateConfig(UpdateConfigDTO configDTO) {
        TbCallConfig config = tbCallConfigService.lambdaQuery().eq(TbCallConfig::getShopId, configDTO.getShopId()).one();
        if (config == null) {
            throw new BadRequestException("未查询到配置信息");
        }
        TbCallConfig tbCallConfig = BeanUtil.copyProperties(configDTO, TbCallConfig.class);
        tbCallConfig.setId(config.getId());
        tbCallConfig.setUpdateTime(DateUtil.date().toInstant());
        return tbCallConfigService.updateById(tbCallConfig);
    }

}
