package cn.iocoder.yudao.module.member.service.diningplates;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender;
import cn.iocoder.yudao.module.member.controller.admin.diningplates.vo.DiningPlatesPageReqVO;
import cn.iocoder.yudao.module.member.controller.admin.diningplates.vo.DiningPlatesSaveReqVO;
import cn.iocoder.yudao.module.member.controller.admin.diningplates.vo.DiningPlatesStoreVO;
import cn.iocoder.yudao.module.member.controller.app.diningplates.vo.AppUserInfo;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserInfoCardVO;
import cn.iocoder.yudao.module.member.dal.dataobject.diningplates.DiningPlatesDO;
import cn.iocoder.yudao.module.member.dal.dataobject.order.DishOrderDO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.member.dal.mysql.diningplates.DiningPlatesMapper;
import cn.iocoder.yudao.module.member.dal.mysql.order.DishOrderMapper;
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
import cn.iocoder.yudao.module.member.service.card.CardService;
import cn.iocoder.yudao.module.member.util.MemberConstants;
import cn.iocoder.yudao.module.system.api.carteen.CarteenApi;
import cn.iocoder.yudao.module.system.api.carteen.dto.CarteenRespDto;
import cn.iocoder.yudao.module.system.api.dish.DishesApi;
import cn.iocoder.yudao.module.system.api.dish.dto.DishesRespDto;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;

/**
 * 餐盘 Service 实现类
 *
 * @author 开发账号
 */
@Service
@Validated
public class DiningPlatesServiceImpl implements DiningPlatesService {

    @Resource
    private DiningPlatesMapper diningPlatesMapper;
    @Resource
    private CardService cardService;
    @Resource
    private MemberUserMapper memberUserMapper;
    @Resource
    private DishOrderMapper dishOrderMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private WebSocketMessageSender webSocketMessageSender;
    @Resource
    private CarteenApi carteenApi;
    @Resource
    private DishesApi dishesApi;

    @Override
    public Long createDiningPlates(DiningPlatesSaveReqVO createReqVO) {
        // 插入
        DiningPlatesDO diningPlates = BeanUtils.toBean(createReqVO, DiningPlatesDO.class);
        List<DiningPlatesDO> diningPlatesDOS = diningPlatesMapper.selectList(Wrappers.<DiningPlatesDO>lambdaQuery()
                .eq(DiningPlatesDO::getDiningPlatesNum, diningPlates.getDiningPlatesNum())
                .eq(DiningPlatesDO::getStoreId, diningPlates.getStoreId()));
        if(CollectionUtil.isNotEmpty(diningPlatesDOS)){
            throw exception(DINING_PLATES_ALREADY);
        }
        diningPlatesMapper.insert(diningPlates);
        // 返回
        return diningPlates.getId();
    }

    @Override
    public void updateDiningPlates(DiningPlatesSaveReqVO updateReqVO) {
        // 校验存在
        validateDiningPlatesExists(updateReqVO.getId());
        // 更新
        DiningPlatesDO updateObj = BeanUtils.toBean(updateReqVO, DiningPlatesDO.class);
        diningPlatesMapper.updateById(updateObj);
    }

    @Override
    public void deleteDiningPlates(Long id) {
        // 校验存在
        validateDiningPlatesExists(id);
        // 删除
        diningPlatesMapper.deleteById(id);
    }

    private void validateDiningPlatesExists(Long id) {
        if (diningPlatesMapper.selectById(id) == null) {
            throw exception(DINING_PLATES_NOT_EXISTS);
        }
    }

    @Override
    public DiningPlatesDO getDiningPlates(Long id) {
        return diningPlatesMapper.selectById(id);
    }

    @Override
    public PageResult<DiningPlatesDO> getDiningPlatesPage(DiningPlatesPageReqVO pageReqVO) {
        PageResult<DiningPlatesDO> diningPlatesDOPageResult = diningPlatesMapper.selectPage(pageReqVO);
        List<DiningPlatesDO> list = diningPlatesDOPageResult.getList();
        if(CollectionUtil.isNotEmpty(list)){
            list.forEach(diningPlatesDO -> {
                diningPlatesDO.setStoreName(carteenApi.getCarteenById(diningPlatesDO.getStoreId()).getStoresName());
            });
        }
        return diningPlatesDOPageResult;
    }

    @Override
    public List<DiningPlatesDO> getDiningPlatesToCharging(Integer time) {
        // 获取当前时间
        LocalDateTime currentTime = LocalDateTime.now();

        // 计算指定分钟之前的时间
        LocalDateTime timeMinutesAgo = currentTime.minusMinutes(time);

        LambdaQueryWrapper<DiningPlatesDO> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DiningPlatesDO::getStatus, DiningPlatesDO.USE)
                .eq(DiningPlatesDO::getPayFlag, DiningPlatesDO.TO_PAY)
                .le(DiningPlatesDO::getBindingTime, timeMinutesAgo);

        List<DiningPlatesDO> diningPlatesDOS = diningPlatesMapper.selectList(wrapper);
        return diningPlatesDOS;
    }

    @Override
    public void updatePayFlag(List<Long> ids, String payStatus) {
        diningPlatesMapper.update(Wrappers.<DiningPlatesDO>lambdaUpdate()
                .set(DiningPlatesDO::getPayFlag, DiningPlatesDO.PAY)
                .in(DiningPlatesDO::getId, ids));
    }

    @Override
    public void updateBind(List<Long> ids) {
        diningPlatesMapper.update(Wrappers.<DiningPlatesDO>lambdaUpdate()
                .set(DiningPlatesDO::getPayFlag, DiningPlatesDO.TO_PAY)
                .set(DiningPlatesDO::getStatus, DiningPlatesDO.FREE)
                .set(DiningPlatesDO::getUserId, null)
                .set(DiningPlatesDO::getBindingTime, null)
                .set(DiningPlatesDO::getOrderId, null)
                .in(DiningPlatesDO::getId, ids));
    }

    @Override
    public List<DiningPlatesDO> getDiningPlatesList() {
        return diningPlatesMapper.selectList();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppUserInfoCardVO bind(String diningPlatesNum, String cardId,Long storeId) {
        DiningPlatesDO diningPlatesDO = diningPlatesMapper.selectOne(Wrappers.<DiningPlatesDO>lambdaQuery()
                .eq(DiningPlatesDO::getDiningPlatesNum, diningPlatesNum)
                .eq(DiningPlatesDO::getStoreId,storeId)
                .last(MemberConstants.LIMIT_ONE));
        checkDiningPlates(diningPlatesDO);
        MemberUserDO memberUserDO = memberUserMapper.selectOne(Wrappers.<MemberUserDO>lambdaQuery()
                .eq(MemberUserDO::getCardId, cardId)
                .last(MemberConstants.LIMIT_ONE));
        checkMemberUser(memberUserDO, false);
        //订单验证
        checkOrder(memberUserDO.getId());

        //余额验证
        BigDecimal money = cardService.getMoneyByUserId(memberUserDO.getId());
        checkMoney(money);

        diningPlatesDO.setUserId(memberUserDO.getId());
        diningPlatesDO.setStatus(DiningPlatesDO.USE);
        diningPlatesDO.setBindingTime(LocalDateTime.now());


        //创建初始订单
        DishOrderDO dishOrderDO = new DishOrderDO();
        dishOrderDO.setDiningPlatesNum(diningPlatesNum)
                .setOrderStatus(DishOrderDO.INCOMPLETE)
                .setUserId(memberUserDO.getId())
                .setStoreId(storeId)
                .setStoreName(carteenApi.getCarteenById(storeId).getStoresName());
        dishOrderMapper.insert(dishOrderDO);

        diningPlatesDO.setOrderId(dishOrderDO.getId());
        diningPlatesMapper.updateById(diningPlatesDO);
        //设置总价
        stringRedisTemplate.opsForValue().set(diningPlatesNum+"-"+storeId, "0");
        AppUserInfoCardVO data = new AppUserInfoCardVO();
        data.setName(memberUserDO.getNickname());
        data.setMoney(money);

        //afterBind(memberUserDO.getId());
        return data;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppUserInfoCardVO bindByFace(String diningPlatesNum, Long faceId,Long storeId) {
        DiningPlatesDO diningPlatesDO = diningPlatesMapper.selectOne(Wrappers.<DiningPlatesDO>lambdaQuery()
                .eq(DiningPlatesDO::getDiningPlatesNum, diningPlatesNum)
                .eq(DiningPlatesDO::getStoreId,storeId)
                .last(MemberConstants.LIMIT_ONE));
        checkDiningPlates(diningPlatesDO);
        MemberUserDO memberUserDO = memberUserMapper.selectOne(Wrappers.<MemberUserDO>lambdaQuery()
                .eq(MemberUserDO::getFaceId, faceId)
                .last(MemberConstants.LIMIT_ONE));
        checkMemberUser(memberUserDO, true);
        //订单验证
        checkOrder(memberUserDO.getId());
        //余额验证
        BigDecimal money = cardService.getMoneyByUserId(memberUserDO.getId());
        checkMoney(money);

        diningPlatesDO.setUserId(memberUserDO.getId());
        diningPlatesDO.setStatus(DiningPlatesDO.USE);
        diningPlatesDO.setBindingTime(LocalDateTime.now());

        //创建初始订单
        DishOrderDO dishOrderDO = new DishOrderDO();
        dishOrderDO.setDiningPlatesNum(diningPlatesNum)
                .setOrderStatus(DishOrderDO.INCOMPLETE)
                .setUserId(memberUserDO.getId())
                .setStoreId(storeId)
                .setStoreName(carteenApi.getCarteenById(storeId).getStoresName());
        dishOrderMapper.insert(dishOrderDO);

        diningPlatesDO.setOrderId(dishOrderDO.getId());
        diningPlatesMapper.updateById(diningPlatesDO);
        //设置总价
        stringRedisTemplate.opsForValue().set(diningPlatesNum+"-"+storeId, "0");
        AppUserInfoCardVO data = new AppUserInfoCardVO();
        data.setName(memberUserDO.getNickname());
        data.setMoney(money);

        //afterBind(memberUserDO.getId());
        return data;
    }

    @Override
    public Boolean checkBind(String diningPlatesNum, Long storeId) {
        DiningPlatesDO diningPlatesDO = diningPlatesMapper.selectOne(Wrappers.<DiningPlatesDO>lambdaQuery()
                .eq(DiningPlatesDO::getDiningPlatesNum, diningPlatesNum)
                .eq(DiningPlatesDO::getStoreId,storeId)
                .last(MemberConstants.LIMIT_ONE));
        if (diningPlatesDO == null || diningPlatesDO.getUserId() == null) {
            return false;
        }
        //刷新绑定时间
        diningPlatesDO.setBindingTime(LocalDateTime.now());
        diningPlatesMapper.updateById(diningPlatesDO);
        return true;
    }

    @Override
    public AppUserInfoCardVO appCheckBind(String diningPlatesNum, Long storeId) {
        DiningPlatesDO diningPlatesDO = diningPlatesMapper.selectOne(Wrappers.<DiningPlatesDO>lambdaQuery()
                .eq(DiningPlatesDO::getDiningPlatesNum, diningPlatesNum)
                .eq(DiningPlatesDO::getStoreId,storeId)
                .last(MemberConstants.LIMIT_ONE));
        if (diningPlatesDO == null || diningPlatesDO.getUserId() == null) {
            throw exception(DINING_PLATES_NOT_BIND);
        }

        MemberUserDO memberUserDO = memberUserMapper.selectById(diningPlatesDO.getUserId());
        AppUserInfoCardVO data = new AppUserInfoCardVO();
        data.setName(memberUserDO.getNickname());
        data.setMoney(cardService.getMoneyByUserId(memberUserDO.getId()));
        return data;
    }

    public void checkDiningPlates(DiningPlatesDO diningPlatesDO) {
        if (diningPlatesDO == null) {
            throw exception(DINING_PLATES_NOT_EXISTS);
        }
        if (diningPlatesDO.getUserId() != null) {
            throw exception(DINING_PLATES_ALREADY_BIND);
        }
    }

    public void checkMemberUser(MemberUserDO memberUserDo, Boolean isFace) {
        if (memberUserDo == null) {
            if (isFace) {
                throw exception(FACE_NOT_BIND_USER);
            } else {
                throw exception(CARD_NOT_BIND_USER);
            }
        }
        //if (StrUtil.isBlank(memberUserDo.getCardId())) {
        //    throw exception(CARD_NOT_BIND);
        //}
    }

    public void checkMoney(BigDecimal money) {
        if (money.compareTo(MemberConstants.ALARM_BALANCE) < 0) {
            throw exception(INSUFFICIENT_BALANCE);
        }
    }

    public void checkOrder(Long userId) {

        LambdaQueryWrapper<DishOrderDO> wrapper = new LambdaQueryWrapper<>();

        wrapper.eq(DishOrderDO::getUserId,userId);
        wrapper.eq(DishOrderDO::getOrderStatus,DishOrderDO.TOCOMPLETE);

        List<DishOrderDO> dishOrderDOS = dishOrderMapper.selectList(wrapper);
        BigDecimal moneyByUserId = cardService.getMoneyByUserId(userId);
        ErrorCode code = new ErrorCode(1_004_099_008, "存在未支付完的订单,待支付金额："+moneyByUserId.abs()+"元");
        if(CollectionUtil.isNotEmpty(dishOrderDOS)){
            throw exception(code);
        }
    }


    @Override
    public List<DiningPlatesStoreVO> getDiningPlatesNum(LocalDateTime localDateTime, Integer time) {
        List<DiningPlatesStoreVO> result  = new ArrayList<>();
        // 获取当前时间
        LocalDateTime endTime = localDateTime;

        // 计算指定分钟之前的时间
        LocalDateTime startTime = endTime.minusMinutes(10);

        List<DiningPlatesDO> diningPlatesDOS = diningPlatesMapper.selectList(Wrappers.<DiningPlatesDO>lambdaQuery()
                .le(DiningPlatesDO::getBindingTime, endTime)
                .gt(DiningPlatesDO::getBindingTime, startTime));
        List<CarteenRespDto> carteenList = carteenApi.getCarteenList();
        carteenList.forEach(dto->{
            DiningPlatesStoreVO diningPlatesStoreVO = new DiningPlatesStoreVO();
            diningPlatesStoreVO.setStoreId(dto.getId());
            diningPlatesStoreVO.setStoreName(dto.getStoresName());
            long count = diningPlatesDOS.stream().filter(diningPlatesDO -> dto.getId().equals(diningPlatesDO.getStoreId())).count();
            diningPlatesStoreVO.setNum(Integer.valueOf(String.valueOf(count)));
            result.add(diningPlatesStoreVO);
        });
        return result;
    }

    /**
     * 绑定之后的操作
     */
    public void afterBind(Long userId) {
        //判断是否已记录
        LocalDate today = LocalDate.now();

        // 一天的开始时间
        LocalDateTime startOfDay = today.atStartOfDay();
        // 一天的结束时间
        LocalDateTime endOfDay = today.atTime(LocalTime.MAX);

        List<DishOrderDO> dishOrderDOS = dishOrderMapper.selectList(Wrappers.<DishOrderDO>lambdaQuery()
                .eq(DishOrderDO::getUserId, userId)
                .between(DishOrderDO::getCreateTime, startOfDay, endOfDay));
        if (dishOrderDOS.size() > 0) {
            return;
        }

        String s = stringRedisTemplate.opsForValue().get(MemberConstants.CUSTOMER_PREFIX);
        if (StrUtil.isBlank(s)) {
            s = "1";
        } else {
            s = String.valueOf(Integer.valueOf(s) + 1);
        }
        stringRedisTemplate.opsForValue().set(MemberConstants.CUSTOMER_PREFIX, s);
        //根据餐盘对应的门店ID发送给对应的用户
        webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), 1L, // 给指定用户
                "demo-message-receive", s);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unbind(String diningPlatesNum,Long storeId) {

        String money = stringRedisTemplate.opsForValue().get(diningPlatesNum+"-"+storeId);
        if(StringUtils.isBlank(money)){
            throw exception(DINING_PLATES_NOT_BIND);
        }

        if(!"0".equals(money)){
            throw exception(DINING_PLATES_CANNOT_UNBIND);
        }
        DiningPlatesDO diningPlatesDO = diningPlatesMapper.selectOne(Wrappers.<DiningPlatesDO>lambdaQuery()
                .eq(DiningPlatesDO::getDiningPlatesNum, diningPlatesNum)
                .eq(DiningPlatesDO::getStoreId,storeId)
                .last("limit 1"));

        dishOrderMapper.update(Wrappers.<DishOrderDO>lambdaUpdate()
                .set(DishOrderDO::getOrderStatus,"1")
                .eq(DishOrderDO::getId,diningPlatesDO.getOrderId()));
        diningPlatesMapper.update(Wrappers.<DiningPlatesDO>lambdaUpdate()
                .set(DiningPlatesDO::getPayFlag, DiningPlatesDO.TO_PAY)
                .set(DiningPlatesDO::getStatus, DiningPlatesDO.FREE)
                .set(DiningPlatesDO::getUserId, null)
                .set(DiningPlatesDO::getBindingTime, null)
                .set(DiningPlatesDO::getOrderId, null)
                .eq(DiningPlatesDO::getId,diningPlatesDO.getId()));


    }

    @Override
    public AppUserInfo getMoney(String diningPlatesNum,Long dishId,Long storeId) {
        DiningPlatesDO diningPlatesDO = diningPlatesMapper.selectOne(Wrappers.<DiningPlatesDO>lambdaQuery()
                .eq(DiningPlatesDO::getDiningPlatesNum, diningPlatesNum)
                .eq(DiningPlatesDO::getStoreId,storeId)
                .last("limit 1"));
        BigDecimal moneyByUserId = cardService.getMoneyByUserId(diningPlatesDO.getUserId());
        MemberUserDO memberUserDO = memberUserMapper.selectById(diningPlatesDO.getUserId());

        DishesRespDto dish = dishesApi.getDish(dishId);

        AppUserInfo appUserInfo = new AppUserInfo();
        appUserInfo.setNickname(memberUserDO.getNickname())
                .setDishesName(dish.getDishesName())
                .setDishesBasePrice(dish.getDishesBasePrice()).setDishesSumPrice(dish.getDishesSumPrice())
                .setMoney(moneyByUserId).setOrderMoney(new BigDecimal(stringRedisTemplate.opsForValue().get(diningPlatesNum+"-"+storeId)));
        return appUserInfo;
    }

    @Override
    public List<String> getBindDiningPlatesList() {
        List<DiningPlatesDO> diningPlatesDOS = diningPlatesMapper.selectList(Wrappers.<DiningPlatesDO>lambdaQuery()
                .isNotNull(DiningPlatesDO::getUserId));
        List<String> list = diningPlatesDOS.stream().map(DiningPlatesDO::getDiningPlatesNum).collect(Collectors.toList());
        return list;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean checkBindAndUnBind(String diningPlatesNum,Long storeId) {
        DiningPlatesDO diningPlatesDO = diningPlatesMapper.selectOne(Wrappers.<DiningPlatesDO>lambdaQuery()
                .eq(DiningPlatesDO::getDiningPlatesNum, diningPlatesNum)
                .eq(DiningPlatesDO::getStoreId, storeId));
        if(diningPlatesDO == null || diningPlatesDO.getUserId() == null){
            return false;
        }else {
            unbind(diningPlatesNum,storeId);
            return true;
        }
    }
}