package com.zanxiang.game.back.serve.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.sd4324530.jtuple.Tuple3;
import com.github.sd4324530.jtuple.Tuples;
import com.zanxiang.game.back.base.pojo.enums.OrderStatusEnum;
import com.zanxiang.game.back.serve.dao.mapper.GameOceanengineOrderLogMapper;
import com.zanxiang.game.back.serve.oceanengine.MiniGameCallback;
import com.zanxiang.game.back.serve.oceanengine.OceanengineCallbackException;
import com.zanxiang.game.back.serve.pojo.dto.GameOceanengineOrderLogDTO;
import com.zanxiang.game.back.serve.pojo.entity.*;
import com.zanxiang.game.back.serve.pojo.enums.BackStatusEnum;
import com.zanxiang.game.back.serve.pojo.enums.BackUnitEnum;
import com.zanxiang.game.back.serve.pojo.vo.GameOceanengineOrderLogVO;
import com.zanxiang.game.back.serve.service.IGameBackPolicyService;
import com.zanxiang.game.back.serve.service.IGameOceanengineBackLogService;
import com.zanxiang.game.back.serve.service.IGameOceanengineOrderLogService;
import com.zanxiang.game.back.serve.service.IGameOceanengineUserLogService;
import com.zanxiang.game.back.serve.utils.BackPolicyUtil;
import com.zanxiang.game.back.serve.utils.OrderUtil;
import com.zanxiang.game.module.base.ServerInfo;
import com.zanxiang.game.module.base.pojo.vo.AgentRpcVO;
import com.zanxiang.game.module.base.rpc.IAgentRpc;
import com.zanxiang.module.util.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
public class GameOceanengineOrderLogServiceImpl extends ServiceImpl<GameOceanengineOrderLogMapper, GameOceanengineOrderLog>
        implements IGameOceanengineOrderLogService {
    @Autowired
    private MiniGameCallback miniGameCallback;
    @Autowired
    private IGameBackPolicyService gameBackPolicyService;
    @Autowired
    private IGameOceanengineUserLogService gameOceanengineUserLogService;
    @Autowired
    private IGameOceanengineBackLogService gameOceanengineBackLogService;
    @DubboReference(providedBy = ServerInfo.SERVER_DUBBO_NAME)
    private IAgentRpc agentRpc;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean callback(GameOceanengineOrderLog orderLog, boolean mustBack, Long backMoneyOfInput) {
        boolean doBack = false;
        Long backMoney;
        String backMsg;
        if (mustBack) {
            doBack = true;
            backMoney = backMoneyOfInput;
            backMsg = "手动触发的回传";
        } else {
            if (!Objects.equals(orderLog.getOrderStatus(), OrderStatusEnum.SUCCESS_PAY.getValue())) {
                // 头条只要回传支付订单
                return true;
            }
            GameBackPolicy gameBackPolicy = gameBackPolicyService.getById(orderLog.getBackPolicyId());
            Tuple3<Boolean, Long, String> backInfo = BackPolicyUtil.backOrder(orderLog.getOrderNo(), gameBackPolicy, orderLog.getAmount(),
                    orderLog.getIsFirstOrder(),
                    orderLog.getPayTime(), orderLog.getRegTime(),
                    orderLog.getOpenId(),
                    new BackPolicyUtil.IBackPolicyCheck() {
                        @Override
                        public long backCountForFixedRate(int numberOfRound, BackUnitEnum backUnit, Boolean firstPolicy) {
                            numberOfRound = numberOfRound - 1;
                            if (backUnit == BackUnitEnum.UNIT_ONCE) {
                                return list(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                        .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                        .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                        .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                        .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                        .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                        .eq(GameOceanengineOrderLog::getIsFirstOrder, firstPolicy)
                                        .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                        .orderByDesc(GameOceanengineOrderLog::getCreateTime)
                                        .last("limit " + numberOfRound)
                                ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
                            } else if (backUnit == BackUnitEnum.UNIT_DAY) {
                                return list(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                        .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                        .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                        .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                        .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                        .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                        .apply(firstPolicy, "date(reg_time) = date(pay_time)")
                                        .apply(!firstPolicy, "date(reg_time) != date(pay_time)")
                                        .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                        .orderByDesc(GameOceanengineOrderLog::getCreateTime)
                                        .last("limit " + numberOfRound)
                                ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
                            } else if (backUnit == BackUnitEnum.UNIT_TIME_DAY) {
                                return list(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                        .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                        .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                        .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                        .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                        .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                        .apply(firstPolicy, "TIMESTAMPDIFF(HOUR, reg_time, pay_time) < 24")
                                        .apply(!firstPolicy, "TIMESTAMPDIFF(HOUR, reg_time, pay_time) >= 24")
                                        .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                        .orderByDesc(GameOceanengineOrderLog::getCreateTime)
                                        .last("limit " + numberOfRound)
                                ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
                            } else if (backUnit == BackUnitEnum.UNIT_TIME_2DAY) {
                                return list(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                        .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                        .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                        .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                        .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                        .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                        .apply(firstPolicy, "TIMESTAMPDIFF(HOUR, reg_time, pay_time) < 48")
                                        .apply(!firstPolicy, "TIMESTAMPDIFF(HOUR, reg_time, pay_time) >= 48")
                                        .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                        .orderByDesc(GameOceanengineOrderLog::getCreateTime)
                                        .last("limit " + numberOfRound)
                                ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
                            } else {
                                throw new RuntimeException("不支持的回传单位[" + backUnit.getValue() + "]");
                            }
                        }

                        @Override
                        public long backCountForUser(BackUnitEnum backUnit, String userId, Boolean firstPolicy) {
                            if (backUnit == BackUnitEnum.UNIT_ONCE) {
                                if (firstPolicy) {
                                    // 首单直接返回 0，必定回传
                                    return 0;
                                }
                                return count(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                        .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                        .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                        .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                        .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                        .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                        .eq(GameOceanengineOrderLog::getBackStatus, BackStatusEnum.SUCCESS.getBackStatus())
                                        .eq(GameOceanengineOrderLog::getOpenId, userId)
                                        .eq(GameOceanengineOrderLog::getIsFirstOrder, firstPolicy)
                                        .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                );
                            } else if (backUnit == BackUnitEnum.UNIT_DAY) {
                                return count(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                        .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                        .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                        .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                        .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                        .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                        .eq(GameOceanengineOrderLog::getBackStatus, BackStatusEnum.SUCCESS.getBackStatus())
                                        .eq(GameOceanengineOrderLog::getOpenId, userId)
                                        .apply(firstPolicy, "date(reg_time) = date(pay_time)")
                                        .apply(!firstPolicy, "date(reg_time) != date(pay_time)")
                                        .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                );
                            } else if (backUnit == BackUnitEnum.UNIT_TIME_DAY) {
                                return count(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                        .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                        .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                        .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                        .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                        .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                        .eq(GameOceanengineOrderLog::getBackStatus, BackStatusEnum.SUCCESS.getBackStatus())
                                        .eq(GameOceanengineOrderLog::getOpenId, userId)
                                        .apply(firstPolicy, "TIMESTAMPDIFF(HOUR, reg_time, pay_time) < 24")
                                        .apply(!firstPolicy, "TIMESTAMPDIFF(HOUR, reg_time, pay_time) >= 24")
                                        .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                );
                            } else if (backUnit == BackUnitEnum.UNIT_TIME_2DAY) {
                                return count(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                        .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                        .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                        .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                        .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                        .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                        .eq(GameOceanengineOrderLog::getBackStatus, BackStatusEnum.SUCCESS.getBackStatus())
                                        .eq(GameOceanengineOrderLog::getOpenId, userId)
                                        .apply(firstPolicy, "TIMESTAMPDIFF(HOUR, reg_time, pay_time) < 48")
                                        .apply(!firstPolicy, "TIMESTAMPDIFF(HOUR, reg_time, pay_time) >= 48")
                                        .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                );
                            } else {
                                throw new RuntimeException("不支持的回传单位[" + backUnit.getValue() + "]");
                            }
                        }

                        @Override
                        public long markUpOfFixedRate(int numberOfRound, Long markUpTime) {
                            numberOfRound = numberOfRound - 1;
                            return list(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                    .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                    .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                    .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                    .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                    .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                    .apply("TIMESTAMPDIFF(MINUTE, reg_time, pay_time) > {0}", markUpTime)
                                    .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                                    .orderByDesc(GameOceanengineOrderLog::getCreateTime)
                                    .last("limit " + numberOfRound)
                            ).stream().filter(log -> log.getBackStatus().equals(BackStatusEnum.SUCCESS.getBackStatus())).count();
                        }

                        @Override
                        public long markUpForUser(String userId, Long markUpTime) {
                            return count(new LambdaQueryWrapper<GameOceanengineOrderLog>()
                                    .eq(GameOceanengineOrderLog::getGameId, orderLog.getGameId())
                                    .eq(GameOceanengineOrderLog::getAppId, orderLog.getAppId())
                                    .eq(GameOceanengineOrderLog::getAccountId, orderLog.getAccountId())
                                    .eq(GameOceanengineOrderLog::getBackPolicyId, gameBackPolicy.getId())
                                    .eq(GameOceanengineOrderLog::getOrderStatus, OrderStatusEnum.SUCCESS_PAY.getValue())
                                    .eq(GameOceanengineOrderLog::getBackStatus, BackStatusEnum.SUCCESS.getBackStatus())
                                    .eq(GameOceanengineOrderLog::getOpenId, userId)
                                    .apply("TIMESTAMPDIFF(MINUTE, reg_time, pay_time) > {0}", markUpTime)
                                    .ne(GameOceanengineOrderLog::getOrderNo, orderLog.getOrderNo())
                            );
                        }
                    });
            doBack = backInfo.first;
            backMoney = backInfo.second;
            backMsg = backInfo.third;
        }
        BackStatusEnum backStatus = BackStatusEnum.NO;
        if (doBack) {
            backStatus = doCallback(orderLog);
        }
        return update(new LambdaUpdateWrapper<GameOceanengineOrderLog>()
                .set(GameOceanengineOrderLog::getBackStatus, backStatus.getBackStatus())
                .set(GameOceanengineOrderLog::getBackMoney, backMoney)
                .set(GameOceanengineOrderLog::getBackMsg, backMsg)
                .eq(GameOceanengineOrderLog::getId, orderLog.getId())
        );
    }

    @Override
    public IPage<GameOceanengineOrderLogVO> oceanengineOrderLogList(GameOceanengineOrderLogDTO dto) {
        IPage<GameOceanengineOrderLog> page = page(dto.toPage(), new LambdaQueryWrapper<GameOceanengineOrderLog>()
                .eq(StringUtils.isNotBlank(dto.getAgentKey()), GameOceanengineOrderLog::getAgentKey, dto.getAgentKey())
                .eq(null != dto.getGameId(), GameOceanengineOrderLog::getGameId, dto.getGameId())
                .eq(null != dto.getAccountId(), GameOceanengineOrderLog::getAccountId, dto.getAccountId())
                .eq(StringUtils.isNotBlank(dto.getAppId()), GameOceanengineOrderLog::getAppId, dto.getAppId())
                .eq(StringUtils.isNotBlank(dto.getOpenId()), GameOceanengineOrderLog::getOpenId, dto.getOpenId())
                .eq(StringUtils.isNotBlank(dto.getOrderNo()), GameOceanengineOrderLog::getOrderNo, dto.getOrderNo())
                .eq(null != dto.getBackStatus(), GameOceanengineOrderLog::getBackStatus, dto.getBackStatus())
                .eq(dto.getIsFirstOrder() != null, GameOceanengineOrderLog::getIsFirstOrder, dto.getIsFirstOrder())
                .eq(null != dto.getOrderStatus(), GameOceanengineOrderLog::getOrderStatus, dto.getOrderStatus())
                .like(StringUtils.isNotBlank(dto.getRoleName()), GameOceanengineOrderLog::getRoleName, dto.getRoleName())
                .apply(dto.getRegPayIntervalTimeMin() != null, "if(pay_time is not null, TIMESTAMPDIFF(minute, reg_time, pay_time), null) >= {0}", dto.getRegPayIntervalTimeMin())
                .apply(dto.getRegPayIntervalTimeMax() != null, "if(pay_time is not null, TIMESTAMPDIFF(minute, reg_time, pay_time), null) <= {0}", dto.getRegPayIntervalTimeMax())
                .orderByDesc(GameOceanengineOrderLog::getCreateTime)
        );
        IPage<GameOceanengineOrderLogVO> result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        if (CollectionUtils.isEmpty(page.getRecords())) {
            return result;
        }
        result.setRecords(toVOBatch(page.getRecords()));
        return result;
    }

    private List<GameOceanengineOrderLogVO> toVOBatch(List<GameOceanengineOrderLog> logList) {
        if (CollectionUtils.isEmpty(logList)) {
            return Collections.emptyList();
        }
        Set<String> agentKeys = logList.stream().map(GameOceanengineOrderLog::getAgentKey).filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        Map<String, AgentRpcVO> agentMap = CollectionUtils.isEmpty(agentKeys) ? Collections.emptyMap() : agentRpc.getByAgentKeys(new ArrayList<>(agentKeys))
                .getData().stream().collect(Collectors.toMap(AgentRpcVO::getAgentKey, Function.identity()));
        return logList.stream().map(log -> {
            GameOceanengineOrderLogVO vo = toVOSimple(log);
            if (StringUtils.isNotBlank(log.getAgentKey())) {
                AgentRpcVO agent = agentMap.get(log.getAgentKey());
                if (agent != null) {
                    vo.setAgentName(agent.getAgentName());
                }
            }
            return vo;
        }).collect(Collectors.toList());

    }

    private GameOceanengineOrderLogVO toVOSimple(GameOceanengineOrderLog log) {
        if (log == null) {
            return null;
        }
        GameOceanengineOrderLogVO vo = BeanUtil.copy(log, GameOceanengineOrderLogVO.class);
        vo.setRegPayIntervalTime(OrderUtil.regPayIntervalTime(log.getPayTime(), log.getRegTime()));
        return vo;
    }

    @Override
    public boolean oceanengineOrderReport(List<Long> ids, Long backMoney) {
        listByIds(ids).stream()
                .filter(order -> !Objects.equals(order.getBackStatus(), BackStatusEnum.SUCCESS.getBackStatus()))
                .forEach(orderLog -> callback(orderLog, true, backMoney));
        return true;
    }

    private BackStatusEnum doCallback(GameOceanengineOrderLog orderLog) {
        GameOceanengineUserLog userLog = gameOceanengineUserLogService.getOne(new LambdaQueryWrapper<GameOceanengineUserLog>()
                .eq(GameOceanengineUserLog::getGameId, orderLog.getGameId())
                .eq(GameOceanengineUserLog::getAppId, orderLog.getAppId())
                .eq(GameOceanengineUserLog::getOpenId, orderLog.getOpenId())
                .eq(GameOceanengineUserLog::getAccountId, orderLog.getAccountId())
                .last("limit 1")
        );
        if (userLog == null) {
            gameOceanengineBackLogService.save(GameOceanengineBackLog.builder()
                    .appId(orderLog.getAppId())
                    .openId(orderLog.getOpenId())
                    .unionId(orderLog.getUnionId())
                    .gameId(orderLog.getGameId())
                    .accountId(orderLog.getAccountId())
                    .clueToken("")
                    .eventType(String.valueOf(MiniGameCallback.EventType.ACTIVE_PAY.getEventType()))
                    .eventLogId(orderLog.getId())
                    .createTime(LocalDateTime.now())
                    .backStatus(BackStatusEnum.FAILED.getBackStatus())
                    .errMsg("找不到用户注册信息")
                    .build());
            return BackStatusEnum.FAILED;
        }
        MiniGameCallback.MiniGameCallbackRequest request = MiniGameCallback.MiniGameCallbackRequest.builder()
                .clue_token(userLog.getClueToken())
                .open_id(orderLog.getOpenId())
                .union_id(orderLog.getUnionId())
                .event_type(String.valueOf(MiniGameCallback.EventType.ACTIVE_PAY.getEventType()))
                .props(MiniGameCallback.MiniGameCallbackRequest.MiniGameCallbackRequestProps.builder()
                        .pay_amount(orderLog.getAmount())
                        .build())
                .build();
        BackStatusEnum backStatus = BackStatusEnum.SUCCESS;
        String errMsg = null;
        try {
            miniGameCallback.doCallback(userLog.getAccountReportUrl(), userLog.getAccountReportToken(), request);
        } catch (OceanengineCallbackException e) {
            log.error(e.getMessage(), e);
            backStatus = BackStatusEnum.FAILED;
            errMsg = e.getMsg();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            backStatus = BackStatusEnum.FAILED;
            errMsg = "系统异常：" + e.getMessage();
        }
        gameOceanengineBackLogService.save(GameOceanengineBackLog.builder()
                .appId(orderLog.getAppId())
                .openId(orderLog.getOpenId())
                .unionId(orderLog.getUnionId())
                .gameId(orderLog.getGameId())
                .accountId(orderLog.getAccountId())
                .clueToken(userLog.getClueToken())
                .eventType(request.getEvent_type())
                .eventLogId(orderLog.getId())
                .createTime(LocalDateTime.now())
                .backStatus(backStatus.getBackStatus())
                .errMsg(errMsg)
                .build());
        return backStatus;
    }
}
