package com.tmerclub.cloud.user.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.tmerclub.cloud.api.auth.constant.SysTypeEnum;
import com.tmerclub.cloud.api.payment.allinpay.order.resp.PayByBackSmsResp;
import com.tmerclub.cloud.api.payment.allinpay.order.resp.WithdrawApplyResp;
import com.tmerclub.cloud.api.payment.constant.PaySysType;
import com.tmerclub.cloud.api.payment.feign.AllinpayFeignClient;
import com.tmerclub.cloud.api.platform.constant.WithdrawFrequency;
import com.tmerclub.cloud.api.platform.feign.ConfigFeignClient;
import com.tmerclub.cloud.api.platform.vo.DistributionConfigApiVO;
import com.tmerclub.cloud.common.constant.AllinpayPayStatus;
import com.tmerclub.cloud.common.constant.AllinpayWithdrawCashStatusType;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.database.dto.PageDTO;
import com.tmerclub.cloud.common.database.util.PageUtil;
import com.tmerclub.cloud.common.database.vo.PageVO;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.util.Arith;
import com.tmerclub.cloud.common.util.Json;
import com.tmerclub.cloud.user.dto.UserWithDrawDTO;
import com.tmerclub.cloud.user.mapper.UserWithdrawCashMapper;
import com.tmerclub.cloud.user.model.UserWithdrawCash;
import com.tmerclub.cloud.user.service.UserWithdrawCashService;
import com.tmerclub.cloud.user.vo.DateUserWithdrawCashVO;
import com.tmerclub.cloud.user.vo.UserWithdrawCashVO;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * @author gaozijie
 * @date 2023-04-24
 */
@Service
public class UserWithdrawCashServiceImpl implements UserWithdrawCashService {

    @Autowired
    private UserWithdrawCashMapper userWithdrawCashMapper;

    @DubboReference
    private AllinpayFeignClient allinpayFeignClient;

    @DubboReference
    private ConfigFeignClient configFeignClient;

    @Override
    public WithdrawApplyResp withdrawApply(UserWithDrawDTO withDrawDTO) {
        // 提现参数校验
        this.checkWithdrawParam(withDrawDTO);
        withDrawDTO.setAmount(Arith.mul(withDrawDTO.getAmount(), 100));
        // 调用通联接口进行余额提现申请
        String bizUserId = String.valueOf(withDrawDTO.getUserId());
        ServerResponseEntity<WithdrawApplyResp> resp = allinpayFeignClient.withdrawApply(
                bizUserId,
                withDrawDTO.getAmount().longValue(),
                withDrawDTO.getBankCardNo(),
                SysTypeEnum.ORDINARY.value()
        );
        if (!resp.isSuccess()) {
            throw new LuckException(resp.getMsg());
        }
        WithdrawApplyResp withdrawApplyResp = resp.getData();
        // 保存用户提现记录
        UserWithdrawCash withdrawCash = new UserWithdrawCash();
        withdrawCash.setUserId(AuthUserContext.get().getUserId());
        withdrawCash.setPaySysType(PaySysType.ALLINPAY.value());
        withdrawCash.setAmount(withDrawDTO.getAmount().longValue());
        withdrawCash.setBankCardNo(withDrawDTO.getBankCardNo());
        withdrawCash.setStatus(AllinpayWithdrawCashStatusType.APPLY_SUCCESS.value());
        withdrawCash.setOrderNo(withdrawApplyResp.getBizOrderNo());
        withdrawCash.setBizOrderNo(withdrawApplyResp.getOrderNo());
        userWithdrawCashMapper.save(withdrawCash);
        // 数据返回
        return withdrawApplyResp;
    }

    @Override
    public PayByBackSmsResp payConfirm(UserWithDrawDTO userWithDrawDTO) {
        // 数据校验
        UserWithdrawCash userWithdrawCash = userWithdrawCashMapper.getByOrderNo(userWithDrawDTO.getBizOrderNo());
        if (Objects.isNull(userWithdrawCash)) {
            throw new LuckException("提现记录不存在");
        }
        if (!Objects.equals(userWithdrawCash.getUserId(), userWithDrawDTO.getUserId())) {
            throw new LuckException("当前提现订单号不属于该用户");
        }
        if (!Objects.equals(userWithdrawCash.getStatus(), AllinpayWithdrawCashStatusType.APPLY_SUCCESS.value())
                && !Objects.equals(userWithdrawCash.getStatus(), AllinpayWithdrawCashStatusType.WAIT_PAY.value())) {
            throw new LuckException("申请状态异常，请刷新重试");
        }
        // 调用通联接口进行余额提现确认支付
        ServerResponseEntity<PayByBackSmsResp> payResponse = allinpayFeignClient.payByBackSms(String.valueOf(userWithdrawCash.getUserId()), userWithDrawDTO.getBizOrderNo(), userWithDrawDTO.getVerificationCode());
        if (!payResponse.isSuccess()) {
            throw new LuckException(payResponse.getMsg());
        }
        PayByBackSmsResp payByBackSmsResp = payResponse.getData();
        String payStatus = payByBackSmsResp.getPayStatus();
        String payFailMessage = payByBackSmsResp.getPayFailMessage();
        if (Objects.equals(payStatus, AllinpayPayStatus.UNPAY.value())) {
            // 收银宝快捷短信验证码输入错误场景，允许商户继续调用确认支付
            userWithdrawCash.setStatus(AllinpayWithdrawCashStatusType.WAIT_PAY.value());
            userWithdrawCash.setPayFailMessage(payFailMessage);
            userWithdrawCashMapper.update(userWithdrawCash);
        }
        return payByBackSmsResp;
    }

    @Override
    public UserWithdrawCash getByBizOrderNo(String bizOrderNo) {
        return userWithdrawCashMapper.getByOrderNo(bizOrderNo);
    }

    @Override
    public void update(UserWithdrawCash userWithdrawCash) {
        userWithdrawCashMapper.update(userWithdrawCash);
    }

    @Override
    public BigDecimal getTotalWithdrawAmount(Long userId) {
        return userWithdrawCashMapper.getTotalWithdrawAmount(userId);
    }

    @Override
    public PageVO<UserWithdrawCashVO> pageUserWithdrawCashVOList(Long userId, PageDTO pageDTO) {
        return PageUtil.doPage(pageDTO, () -> userWithdrawCashMapper.listUserWithdrawCashVOList(userId));
    }

    @Override
    public PageVO<DateUserWithdrawCashVO> pageDateUserWithdrawCashVOList(Long userId, PageDTO pageDTO) {
        // 分页查询用户提现记录
        PageVO<UserWithdrawCashVO> withdrawCashPage = this.pageUserWithdrawCashVOList(userId, pageDTO);
        PageVO<DateUserWithdrawCashVO> dateWithdrawCashPage = new PageVO<>();
        dateWithdrawCashPage.setTotal(withdrawCashPage.getTotal());
        dateWithdrawCashPage.setPages(withdrawCashPage.getPages());
        if (CollectionUtils.isEmpty(withdrawCashPage.getList())) {
            dateWithdrawCashPage.setList(new ArrayList<>(Constant.INITIAL_CAPACITY));
            return dateWithdrawCashPage;
        }
        // 取出数据，对里面的数据按createTime的年月进行分组
        Map<Date, List<UserWithdrawCashVO>> dateUserWithdrawCashMap = new HashMap<>(16);
        DateTime beginOfMonth;
        List<UserWithdrawCashVO> userWithdrawCashVOList;
        for (UserWithdrawCashVO userWithdrawCashVO : withdrawCashPage.getList()) {
            beginOfMonth = DateUtil.beginOfMonth(userWithdrawCashVO.getCreateTime());
            userWithdrawCashVOList = dateUserWithdrawCashMap.getOrDefault(beginOfMonth, new ArrayList<>(Constant.INITIAL_CAPACITY));
            userWithdrawCashVOList.add(userWithdrawCashVO);
            dateUserWithdrawCashMap.put(beginOfMonth, userWithdrawCashVOList);
        }
        // 数据封装为DateUserWithdrawCashVO
        List<DateUserWithdrawCashVO> dateUserWithdrawCashVOList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        DateUserWithdrawCashVO dateUserWithdrawCashVO;
        for (Map.Entry<Date, List<UserWithdrawCashVO>> entry : dateUserWithdrawCashMap.entrySet()) {
            dateUserWithdrawCashVO = new DateUserWithdrawCashVO();
            dateUserWithdrawCashVO.setDate(entry.getKey());
            dateUserWithdrawCashVO.setWithdrawCashVOs(entry.getValue());
            dateUserWithdrawCashVOList.add(dateUserWithdrawCashVO);
        }
        // 封装到新pageVO，并进行返回
        dateWithdrawCashPage.setList(dateUserWithdrawCashVOList);
        return dateWithdrawCashPage;
    }

    private void checkWithdrawParam(UserWithDrawDTO userWithDrawDTO) {
        // 获取提现配置(分销配置)
        ServerResponseEntity<String> config = configFeignClient.getConfig(Constant.DISTRIBUTION_CONFIG);
        if (!config.isSuccess() || Objects.isNull(config.getData())) {
            throw new LuckException(config.getMsg());
        }
        DistributionConfigApiVO distributionConfig = Json.parseObject(config.getData(), DistributionConfigApiVO.class);
        // 校验提现金额是否超出限制
        BigDecimal amount = BigDecimal.valueOf(userWithDrawDTO.getAmount());
        if (amount.compareTo(distributionConfig.getAmountMax()) > 0) {
            throw new LuckException("提现金额大于最大提现金额");
        }
        if (amount.compareTo(distributionConfig.getAmountMin()) < 0) {
            throw new LuckException("提现金额小于最小提现金额");
        }
        // 校验提现时间频率
        Integer frequency = distributionConfig.getFrequency();
        if (!Objects.equals(frequency, WithdrawFrequency.NOT_LIMIT.getValue())) {
            LocalDateTime startTime = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
            WithdrawFrequency withdrawFrequency;
            if (Objects.equals(frequency, WithdrawFrequency.EVERY_DAY.getValue())) {
                // 每日
                withdrawFrequency = WithdrawFrequency.EVERY_DAY;
            } else if (Objects.equals(frequency, WithdrawFrequency.EVERY_WEEK.getValue())) {
                // 每周
                startTime = startTime.plusDays(-startTime.getDayOfWeek().getValue() + 1);
                withdrawFrequency = WithdrawFrequency.EVERY_WEEK;
            } else if (Objects.equals(frequency, WithdrawFrequency.EVERY_MONTH.getValue())) {
                // 每月
                startTime = startTime.plusDays(-startTime.getDayOfMonth() + 1);
                withdrawFrequency = WithdrawFrequency.EVERY_MONTH;
            } else {
                throw new LuckException("提现频率配置错误");
            }
            // 校验次数
            Integer times = userWithdrawCashMapper.countWithdrawTimes(userWithDrawDTO.getUserId(), startTime);
            if (times >= distributionConfig.getNumber()) {
                throw new LuckException("提现次数为" + withdrawFrequency.getDesc() + "只能提现" + distributionConfig.getNumber() + "次");
            }
        }
    }
}
