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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.sd4324530.jtuple.Tuple2;
import com.zanxiang.game.back.base.pojo.vo.OrderBackQueryRpcVO;
import com.zanxiang.game.back.serve.dao.mapper.GameOceanengineAppCallbackXgltMapper;
import com.zanxiang.game.back.serve.pojo.entity.*;
import com.zanxiang.game.back.serve.service.IGameOceanengineAppBackLogXgltService;
import com.zanxiang.game.back.serve.service.IGameOceanengineAppCallbackXgltService;
import com.zanxiang.game.back.serve.service.IGameOceanengineAppUserLogService;
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.DateUtil;
import com.zanxiang.module.util.JsonUtil;
import com.zanxiang.module.util.URIUtil;
import com.zanxiang.module.util.encryption.Md5Util;
import com.zanxiang.module.util.pojo.ResultVO;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class GameOceanengineAppCallbackXgltServiceImpl extends ServiceImpl<GameOceanengineAppCallbackXgltMapper, GameOceanengineAppCallbackXglt>
        implements IGameOceanengineAppCallbackXgltService {

    @DubboReference(providedBy = ServerInfo.SERVER_DUBBO_NAME)
    private IAgentRpc agentRpc;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private IGameOceanengineAppUserLogService gameOceanengineAppUserLogService;

    @Autowired
    private IGameOceanengineAppBackLogXgltService gameOceanengineAppBackLogXgltService;

    @Override
    public boolean callback(Map<String, Object> paramMap) {
        try {
            GameOceanengineAppCallbackXglt callback = JsonUtil.toObj(JsonUtil.toString(paramMap), GameOceanengineAppCallbackXglt.class);
            callback.setDay(callback.getTs() == null || callback.getTs() < 1000 ? LocalDate.now() : DateUtil.milliToLocalDate(callback.getTs()));
            callback.setCreateTime(LocalDateTime.now());
            save(callback);
        } catch (Exception e) {
            log.error("接收到头条星广联投测链接数据保存异常, paramMap : {}, errorMsg : {}", JsonUtil.toString(paramMap), e.getMessage(), e);
        }
        return true;
    }

    @Override
    public String getCallBackParamByUser(GameOceanengineAppUserLog userLog) {
        try {
            //没有渠道标识, 无法匹配
            if (StringUtils.isBlank(userLog.getAgentKey())) {
                return null;
            }
            ResultVO<List<AgentRpcVO>> resultVO = agentRpc.getByAgentKeys(Collections.singletonList(userLog.getAgentKey()));
            Long demandId = Optional.ofNullable(resultVO).map(ResultVO::getData)
                    .filter(list -> !list.isEmpty()).map(list -> list.get(0))
                    .map(AgentRpcVO::getXgltDemandId).orElse(null);
            if (demandId == null) {
                return null;
            }
            GameOceanengineAppCallbackXglt callBack = this.getCallBack(demandId, userLog.getAndroidId(), userLog.getOaid(),
                    userLog.getImei(), userLog.getActiveTime());
            if (callBack == null || StringUtils.isBlank(callBack.getCallbackParam())) {
                return null;
            }
            Map<String, String> map = new HashMap<>(2);
            map.put("demandId", demandId.toString());
            map.put("callBack", callBack.getCallbackParam());
            return JsonUtil.toString(map);
        } catch (Exception e) {
            log.error("头条APP玩家匹配星广联投监测链接数据异常, userLogId : {}, e : {}", JsonUtil.toString(userLog), e.getMessage(), e);
        }
        return null;
    }

    private GameOceanengineAppCallbackXglt getCallBack(Long demandId, String androidId, String oaid, String imei, LocalDateTime regTime) {
        //星广联投任务id不存在, 不可匹配
        if (demandId == null) {
            return null;
        }
        LambdaQueryWrapper<GameOceanengineAppCallbackXglt> queryWrapper = new LambdaQueryWrapper<GameOceanengineAppCallbackXglt>()
                .lt(GameOceanengineAppCallbackXglt::getTs, DateUtil.localDateTimeToMilli(regTime))
                .eq(GameOceanengineAppCallbackXglt::getDemandId, demandId)
                .orderByDesc(GameOceanengineAppCallbackXglt::getTs)
                .last("limit 1");
        //条件匹配
        queryWrapper.and(qw -> {
            boolean hasCondition = false;
            //安卓id匹配
            if (StringUtils.isNoneBlank(androidId)) {
                qw.or().eq(GameOceanengineAppCallbackXglt::getAndroidIdMd5, Md5Util.encrypt32(androidId));
                hasCondition = true;
            }
            //oaid匹配
            if (StringUtils.isNoneBlank(oaid)) {
                qw.or().eq(GameOceanengineAppCallbackXglt::getOaidMd5, Md5Util.encrypt32(oaid));
                hasCondition = true;
            }
            //imei匹配
            if (StringUtils.isNoneBlank(imei)) {
                qw.or().eq(GameOceanengineAppCallbackXglt::getImeiMd5, Md5Util.encrypt32(imei));
                hasCondition = true;
            }
            // 避免无条件的OR查询
            if (!hasCondition) {
                qw.or().apply("1=0");
            }
        });
        //返回星广联投监测链接数据
        return super.getOne(queryWrapper);
    }

    @Async
    @Override
    public void regBack(GameOceanengineAppUserLog userLog) {
        //注册只回传一次
        if (gameOceanengineAppBackLogXgltService.count(new LambdaQueryWrapper<GameOceanengineAppBackLogXglt>()
                .eq(GameOceanengineAppBackLogXglt::getGameId, userLog.getGameId())
                .eq(GameOceanengineAppBackLogXglt::getUserId, userLog.getUserId())
                .eq(GameOceanengineAppBackLogXglt::getEventType, EventType.Event_ACTIVE.getEventType())
        ) > 0) {
            return;
        }
        //判断是否关联星广联投, 未关联, 跳过
        Tuple2<Long, String> tuple2 = this.getCallBackParam(userLog);
        if (tuple2 == null) {
            return;
        }
        //线程锁, 避免并发或者多次请求
        String lockKey = "game-back:TtAppXgltBack:" + userLog.getGameId() + ":" + userLog.getUserId() + ":"
                + EventType.Event_ACTIVE.getEventType();
        if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 30, TimeUnit.SECONDS))) {
            return;
        }
        //执行回传
        Tuple2<String, String> resultTuple2 = this.doCallBack(tuple2.second, EventType.Event_ACTIVE, null);
        //回传记录
        GameOceanengineAppBackLogXglt gameOceanengineAppBackLogXglt = GameOceanengineAppBackLogXglt.builder()
                .demandId(tuple2.first)
                .gameId(userLog.getGameId())
                .userId(userLog.getUserId())
                .eventType(EventType.Event_ACTIVE.getEventType())
                .callBackUri(resultTuple2.first)
                .callBackResult(resultTuple2.second)
                .createTime(LocalDateTime.now())
                .build();
        //保存记录
        gameOceanengineAppBackLogXgltService.save(gameOceanengineAppBackLogXglt);
    }

    @Async
    @Override
    public void createRoleBack(GameOceanengineAppRoleRegisterLog roleLog) {
        //判断创角只回传一次
        if (gameOceanengineAppBackLogXgltService.count(new LambdaQueryWrapper<GameOceanengineAppBackLogXglt>()
                .eq(GameOceanengineAppBackLogXglt::getGameId, roleLog.getGameId())
                .eq(GameOceanengineAppBackLogXglt::getUserId, roleLog.getUserId())
                .eq(GameOceanengineAppBackLogXglt::getRoleId, roleLog.getRoleId())
                .eq(GameOceanengineAppBackLogXglt::getEventType, EventType.Event_REGISTER.getEventType())
        ) > 0) {
            return;
        }
        //判断是否关联星广联投, 未关联, 跳过
        Tuple2<Long, String> tuple2 = this.getCallBackParam(roleLog.getGameId(), roleLog.getUserId(), roleLog.getAccountId());
        if (tuple2 == null) {
            return;
        }
        //线程锁, 避免并发或者多次请求
        String lockKey = "game-back:TtAppXgltBack:" + roleLog.getGameId() + ":" + roleLog.getUserId() + ":"
                + roleLog.getRoleId() + ":" + EventType.Event_REGISTER.getEventType();
        if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 30, TimeUnit.SECONDS))) {
            return;
        }
        //执行回传
        Tuple2<String, String> resultTuple2 = this.doCallBack(tuple2.second, EventType.Event_REGISTER, null);
        //回传记录
        GameOceanengineAppBackLogXglt gameOceanengineAppBackLogXglt = GameOceanengineAppBackLogXglt.builder()
                .demandId(tuple2.first)
                .gameId(roleLog.getGameId())
                .userId(roleLog.getUserId())
                .roleId(roleLog.getRoleId())
                .eventType(EventType.Event_REGISTER.getEventType())
                .callBackUri(resultTuple2.first)
                .callBackResult(resultTuple2.second)
                .createTime(LocalDateTime.now())
                .build();
        //保存记录
        gameOceanengineAppBackLogXgltService.save(gameOceanengineAppBackLogXglt);
    }

    @Async
    @Override
    public void orderPayBack(GameOceanengineAppOrderLog orderLog, OrderBackQueryRpcVO orderBackQueryRpcVO) {
        //判断是否关联星广联投, 未关联, 跳过
        Tuple2<Long, String> tuple2 = this.getCallBackParam(orderLog.getGameId(), orderLog.getUserId(), orderLog.getAccountId());
        if (tuple2 == null) {
            return;
        }
        //单个订单只回传一次
        if (gameOceanengineAppBackLogXgltService.count(new LambdaQueryWrapper<GameOceanengineAppBackLogXglt>()
                .eq(GameOceanengineAppBackLogXglt::getGameId, orderLog.getGameId())
                .eq(GameOceanengineAppBackLogXglt::getUserId, orderLog.getUserId())
                .eq(GameOceanengineAppBackLogXglt::getOrderId, orderLog.getOrderNo())
                .eq(GameOceanengineAppBackLogXglt::getEventType, EventType.Event_PAY.getEventType())
        ) > 0) {
            return;
        }
        //线程锁, 避免并发或者多次请求
        String lockKey = "game-back:TtAppXgltBack:" + orderLog.getGameId() + ":" + orderLog.getUserId() + ":"
                + orderLog.getRoleId() + ":" + orderLog.getOrderNo() + ":" + EventType.Event_PAY.getEventType();
        if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 30, TimeUnit.SECONDS))) {
            return;
        }
        //回传的金额列表
        List<Long> backMoneyList = orderBackQueryRpcVO.getBackMoney();
        //循环执行回传
        backMoneyList.forEach(amount -> {
            //执行回传
            Tuple2<String, String> resultTuple2 = this.doCallBack(tuple2.second, EventType.Event_PAY, amount);
            //回传记录
            GameOceanengineAppBackLogXglt gameOceanengineAppBackLogXglt = GameOceanengineAppBackLogXglt.builder()
                    .demandId(tuple2.first)
                    .gameId(orderLog.getGameId())
                    .userId(orderLog.getUserId())
                    .roleId(orderLog.getRoleId())
                    .orderId(orderLog.getOrderNo())
                    .eventType(EventType.Event_PAY.getEventType())
                    .callBackUri(resultTuple2.first)
                    .callBackResult(resultTuple2.second)
                    .createTime(LocalDateTime.now())
                    .build();
            //保存记录
            gameOceanengineAppBackLogXgltService.save(gameOceanengineAppBackLogXglt);
        });
    }

    private Tuple2<Long, String> getCallBackParam(Long gameId, String userId, Long accountId) {
        GameOceanengineAppUserLog userLog = gameOceanengineAppUserLogService.getOne(new LambdaQueryWrapper<GameOceanengineAppUserLog>()
                .eq(GameOceanengineAppUserLog::getGameId, gameId)
                .eq(GameOceanengineAppUserLog::getUserId, userId)
                .eq(GameOceanengineAppUserLog::getAccountId, accountId)
                .orderByDesc(GameOceanengineAppUserLog::getCreateTime)
                .last("limit 1"));
        //参数解析返回
        return this.getCallBackParam(userLog);
    }

    private Tuple2<Long, String> getCallBackParam(GameOceanengineAppUserLog userLog) {
        //参数判断
        if (userLog == null || StringUtils.isBlank(userLog.getXgltCallback())) {
            return null;
        }
        //解析
        Map<String, String> map = JsonUtil.toMap(userLog.getXgltCallback(), Map.class, String.class);
        //返回参数
        return Tuple2.with(Long.valueOf(map.get("demandId")), map.get("callBack"));
    }

    private Tuple2<String, String> doCallBack(String callback, EventType eventType, Long amount) {
        Map<String, String> paramMap = new HashMap<>(3);
        paramMap.put("callback", callback);
        paramMap.put("event_type", eventType.getEventType());
        //付费回传
        if (Objects.equals(eventType, EventType.Event_PAY) && amount != null) {
            Map<String, String> payAmount = Collections.singletonMap("pay_amount", amount.toString());
            paramMap.put("props", URIUtil.encodeURIComponent(JsonUtil.toString(payAmount)));
        }
        String uri = URIUtil.fillUrlParams("https://ad.oceanengine.com/track/activate/", paramMap, false);
        String result;
        try {
            result = restTemplate.getForObject(uri, String.class);
        } catch (Exception e) {
            log.error("头条星广联投回调异常, uri : {}, errorMsg : {}", uri, e.getMessage(), e);
            result = "头条星广联投回传接口调用异常";
        }
        return Tuple2.with(uri, result);
    }

    @Getter
    @AllArgsConstructor
    public enum EventType {

        /**
         * 激活
         */
        Event_ACTIVE("0"),

        /**
         * 注册
         */
        Event_REGISTER("1"),

        /**
         * 付费
         * props:{"pay_amount":"xxx"}, xxx单位为分
         */
        Event_PAY("2");

        private final String eventType;
    }
}