package co.yixiang.yshop.module.member.service.signinrecord;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import co.yixiang.yshop.framework.common.pojo.PageResult;
import co.yixiang.yshop.framework.common.util.date.DateUtils;
import co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils;
import co.yixiang.yshop.module.member.api.integralRule.IntegralRuleDTO;
import co.yixiang.yshop.module.member.controller.admin.signinrecord.vo.*;
import co.yixiang.yshop.module.member.controller.app.signinrecord.vo.SignInInfoVO;
import co.yixiang.yshop.module.member.convert.signinrecord.SignInRecordConvert;
import co.yixiang.yshop.module.member.dal.dataobject.integralrule.IntegralRuleDO;
import co.yixiang.yshop.module.member.dal.dataobject.signinrecord.SignInRecordDO;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.module.member.dal.mysql.signinrecord.SignInRecordMapper;
import co.yixiang.yshop.module.member.enums.BillDetailEnum;
import co.yixiang.yshop.module.member.service.integralrule.IntegralRuleService;
import co.yixiang.yshop.module.member.service.integralrule.strategy.IntegralRuleContext;
import co.yixiang.yshop.module.member.service.user.UserService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.jetbrains.annotations.NotNull;
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.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

import static co.yixiang.yshop.framework.common.constant.ShopConstants.DAY_FORMAT_STR;
import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.*;

/**
 * 签到记录 Service 实现类
 *
 * @author admin
 */
@Service
@Validated
public class SignInRecordServiceImpl implements SignInRecordService {

    @Resource
    private SignInRecordMapper signInRecordMapper;
    @Resource
    private IntegralRuleContext integralRuleContext;
    @Resource
    private IntegralRuleService integralRuleService;
    @Resource
    private UserService userService;

    @Override
    public Long createSignInRecord(SignInRecordCreateReqVO createReqVO) {
        // 插入
        SignInRecordDO signInRecord = SignInRecordConvert.INSTANCE.convert(createReqVO);
        signInRecordMapper.insert(signInRecord);
        // 返回
        return signInRecord.getId();
    }

    @Override
    public void updateSignInRecord(SignInRecordUpdateReqVO updateReqVO) {
        // 校验存在
        validateSignInRecordExists(updateReqVO.getId());
        // 更新
        SignInRecordDO updateObj = SignInRecordConvert.INSTANCE.convert(updateReqVO);
        signInRecordMapper.updateById(updateObj);
    }

    @Override
    public void deleteSignInRecord(Long id) {
        // 校验存在
        validateSignInRecordExists(id);
        // 删除
        signInRecordMapper.deleteById(id);
    }

    private void validateSignInRecordExists(Long id) {
        if (signInRecordMapper.selectById(id) == null) {
            throw exception(SIGN_IN_RECORD_NOT_EXISTS);
        }
    }

    @Override
    public SignInRecordDO getSignInRecord(Long id) {
        return signInRecordMapper.selectById(id);
    }

    @Override
    public List<SignInRecordDO> getSignInRecordList(Collection<Long> ids) {
        return signInRecordMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<SignInRecordDO> getSignInRecordPage(SignInRecordPageReqVO pageReqVO) {
        return signInRecordMapper.selectPage(pageReqVO);
    }

    @Override
    public List<SignInRecordDO> getSignInRecordList(SignInRecordExportReqVO exportReqVO) {
        return signInRecordMapper.selectList(exportReqVO);
    }

    @Override
    public Integer countByUserIdThisWeek(Long userId) {
        List<SignInRecordDO> signInRecordList = getSignInRecord(userId, LocalDateTimeUtils.weekStartTime(), LocalDateTimeUtils.weekEndTime());
        if (CollectionUtil.isEmpty(signInRecordList)) {
            return 0;
        }
        Map<String, SignInRecordDO> recordMap = signInRecordList.stream().collect(Collectors.toMap(item -> item.getCreateTime().format(DateTimeFormatter.ofPattern(DAY_FORMAT_STR)), item -> item));
        AtomicInteger count = new AtomicInteger(0);
        DateTime now = DateUtil.date();
        List<String> dateTimes = CollectionUtil.reverse(DateUtil.rangeToList(DateUtil.beginOfWeek(now), DateUtil.endOfDay(now), DateField.DAY_OF_WEEK))
                .stream()
                .filter(item -> !DateUtils.isSameDay(item, now))
                .map(item -> DateUtil.format(item, DAY_FORMAT_STR))
                .collect(Collectors.toList());
        for (String dateTime : dateTimes) {
            if (recordMap.containsKey(dateTime)) {
                count.incrementAndGet();
            } else {
                break;
            }
        }
        //判断今天是否签到
        String nowStr = DateUtil.format(now, DAY_FORMAT_STR);
        if (recordMap.containsKey(nowStr)) {
            count.incrementAndGet();
        }
        return count.get();
    }

    @Override
    public SignInInfoVO signInInfo(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
        SignInInfoVO signInInfoVO = new SignInInfoVO();

        MemberUserDO user = userService.getUser(userId);
        signInInfoVO.setIntegral(user.getIntegral());

        //今日是否签到
        Boolean exists = this.existSignInToday(userId);
        signInInfoVO.setTodaySignIn(exists);

        //累计签到天数
        Integer count = countByUserIdThisWeek(userId);
        signInInfoVO.setSignInDays(count);

        //签到记录
        List<SignInRecordDO> list = getSignInRecord(userId, startTime, endTime);
        List<SignInRecordRespVO> signList = SignInRecordConvert.INSTANCE.convertList(list);
        Map<LocalDate, SignInRecordRespVO> signMap = signList.stream().collect(Collectors.toMap(item -> item.getCreateTime().toLocalDate(), Function.identity()));

        //本周日期列表
        LocalDate nowDate = LocalDate.now();
        //查询签到相关积分规则
        List<IntegralRuleDO> integralRuleList = integralRuleService.getIntegralRuleByTypes(ListUtil.of(BillDetailEnum.SIGN.getValue()));
        //每日签到规则
        IntegralRuleDO everydayIntegralRuleDO = integralRuleList.stream().filter(item -> item.getAttribute1().equals("1")).findFirst().orElse(new IntegralRuleDO(0));
        AtomicInteger predictCount = new AtomicInteger(count);

        List<DateTime> dateTimes = DateUtil.rangeToList(DateUtils.of(startTime), DateUtils.of(endTime), DateField.DAY_OF_WEEK);
        signList = dateTimes.stream().map(item -> {
            LocalDate localDate = DateUtils.of(item).toLocalDate();
            SignInRecordRespVO signInRecordRespVO = signMap.get(localDate);
            // 当天已经签到
            if (Objects.nonNull(signInRecordRespVO)) {
                signInRecordRespVO.setIsSign(Boolean.TRUE);
            } else { //未签到
                signInRecordRespVO = new SignInRecordRespVO();
                signInRecordRespVO.setIsSign(Boolean.FALSE);
                signInRecordRespVO.setCreateTime(DateUtils.of(item));
                signInRecordRespVO.setIntegral(BigDecimal.ZERO);
                //可签到记录
                if (localDate.isEqual(nowDate) || localDate.isAfter(nowDate)) {
                    //当前连续签到天数
                    int curCount = predictCount.incrementAndGet();
                    IntegralRuleDO thisDayRule = integralRuleList.stream().filter(rule -> Integer.valueOf(rule.getAttribute1()).equals(curCount)).findFirst().orElse(everydayIntegralRuleDO);
                    signInRecordRespVO.setIntegral(BigDecimal.valueOf(thisDayRule.getIntegral()));
                }
            }
            return signInRecordRespVO;
        }).collect(Collectors.toList());

        signInInfoVO.setSignInRecordList(signList);
        return signInInfoVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public BigDecimal signIn(Long userId) {
        //校验今日是否签到
        verifyExistSignInToday(userId);
        //获取当前签到规则
        IntegralRuleDO currentIntegralRule = getIntegralRule(userId);
        //领取积分
        IntegralRuleDTO rule = new IntegralRuleDTO();
        rule.setUserId(userId);
        rule.setType(BillDetailEnum.SIGN.getValue());
        rule.setAttribute1(currentIntegralRule.getAttribute1());
        rule.setIntegral(currentIntegralRule.getIntegral());
        rule.setEnable(currentIntegralRule.getEnable());
        BigDecimal integral = integralRuleContext.execute(BillDetailEnum.SIGN, rule);
        //签到记录
        SignInRecordDO signInRecordDO = new SignInRecordDO();
        signInRecordDO.setUserId(userId);
        signInRecordDO.setIntegral(integral);
        signInRecordMapper.insert(signInRecordDO);
        return integral;
    }

    /**
     * 获取本次签到对应签到规则
     *
     * @param userId /
     * @return /
     */
    @NotNull
    private IntegralRuleDO getIntegralRule(Long userId) {
        //查询签到相关积分规则
        List<IntegralRuleDO> integralRuleList = integralRuleService.getIntegralRuleByTypes(ListUtil.of(BillDetailEnum.SIGN.getValue()));
        //每日签到规则
        IntegralRuleDO everydayIntegralRuleDO = integralRuleList.stream().filter(item -> item.getAttribute1().equals("1")).findFirst().orElse(new IntegralRuleDO(0,true));
        //当前签到天数
        Integer count = countByUserIdThisWeek(userId) + 1;
        //获取当前签到规则
        return integralRuleList.stream().filter(item -> Integer.valueOf(item.getAttribute1()).equals(count)).findFirst().orElse(everydayIntegralRuleDO);
    }


    /**
     * 查询用户签到记录
     *
     * @param userId /
     * @return /
     */
    private List<SignInRecordDO> getSignInRecord(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
        SignInRecordExportReqVO exportReqVO = new SignInRecordExportReqVO();
        exportReqVO.setUserId(userId);
        LocalDateTime[] createTime = new LocalDateTime[2];
        createTime[0] = startTime;
        createTime[1] = endTime;
        exportReqVO.setCreateTime(createTime);
        return this.getSignInRecordList(exportReqVO);
    }

    /**
     * 校验今日是否签到
     *
     * @param userId /
     */
    private void verifyExistSignInToday(Long userId) {
        Boolean exists = this.existSignInToday(userId);
        if (exists) {
            throw exception(TODAY_SIGN_IN_RECORD_EXISTS);
        }
    }

    /**
     * 今日是否存在签到
     *
     * @param userId /
     * @return /
     */
    private Boolean existSignInToday(Long userId) {
        return signInRecordMapper.exists(Wrappers.<SignInRecordDO>lambdaQuery()
                .eq(SignInRecordDO::getUserId, userId)
                .between(SignInRecordDO::getCreateTime, DateUtil.beginOfDay(DateUtil.date()), DateUtil.date())
        );
    }

}
