package com.zanxiang.game.module.sdk.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.sd4324530.jtuple.Tuple2;
import com.zanxiang.game.back.base.ServerInfo;
import com.zanxiang.game.back.base.pojo.dto.TencentAppApiUserAgentQueryRpcDTO;
import com.zanxiang.game.back.base.rpc.ITencentAppApiBackRpc;
import com.zanxiang.game.back.base.rpc.ITencentMiniGameBackRpc;
import com.zanxiang.game.module.base.pojo.enums.GameCategoryEnum;
import com.zanxiang.game.module.mybatis.entity.*;
import com.zanxiang.game.module.mybatis.mapper.AgentMapper;
import com.zanxiang.game.module.sdk.enums.KafkaEventTrackEnum;
import com.zanxiang.game.module.sdk.pojo.param.UserData;
import com.zanxiang.game.module.sdk.service.*;
import com.zanxiang.module.util.DateUtil;
import com.zanxiang.module.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.util.function.Tuple3;
import reactor.util.function.Tuples;

import java.time.LocalDateTime;
import java.util.*;

/**
 * @author bilingfeng
 */
@Slf4j
@Service
public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent> implements IAgentService {

    @DubboReference(providedBy = ServerInfo.SERVER_DUBBO_NAME)
    private ITencentAppApiBackRpc tencentAppApiBackRpc;

    @DubboReference(providedBy = ServerInfo.SERVER_DUBBO_NAME)
    private ITencentMiniGameBackRpc tencentMiniGameBackRpc;

    @Autowired
    private IUserService userService;

    @Autowired
    private IUserLoginLogService userLoginLogService;

    @Autowired
    private ICallBackService callBackService;

    @Autowired
    private IUserAgentLogService userAgentLogService;

    @Autowired
    private IUserVisitLogService userVisitLogService;

    @Autowired
    private IGameService gameService;

    @Autowired
    private IGameExtService gameExtService;

    @Autowired
    private IKafkaService kafkaService;

    @Autowired
    private IUserAppletService userAppletService;

    @Override
    public void userAgentUpdate(User user, UserData userData) {
        try {
            String channel = userData.getChannel();
            //渠道参数为空
            if (Strings.isBlank(channel)) {
                return;
            }
            //判断用户是否超过30天未登录
            List<UserLoginLog> userLoginLogList = userLoginLogService.list(new LambdaQueryWrapper<UserLoginLog>()
                    .eq(UserLoginLog::getUserId, user.getId())
                    .orderByDesc(UserLoginLog::getCreateTime)
                    .last("limit 1"));
            if (CollectionUtils.isEmpty(userLoginLogList)) {
                return;
            }
            //最近登录时间
            LocalDateTime lastLoginTime = userLoginLogList.get(0).getCreateTime();
            //最近登录未超过30天
            if (lastLoginTime.plusDays(60L).isAfter(LocalDateTime.now())) {
                return;
            }
            //30天未登录, 更新渠道
            Game game = gameService.getById(user.getGameId());
            //渠道id, 链接参数, 分享人id
            Tuple3<Long, Map<String, String>, String> tuple3 = this.getUserAgentId(game, userData, user.getOpenId());
            //查询渠道
            Agent agent = super.getById(tuple3.getT1());
            if (agent == null) {
                return;
            }
            //保存旧的渠道信息
            Tuple3<Long, Long, String> oldAgentChannel = Tuples.of(user.getId(), user.getAgentId(), user.getChannel());
            //更新用户信息
            user.setAgentId(agent.getId());
            user.setUpdateTime(LocalDateTime.now());
            //App的渠道保存解析后的参数, 因为涉及到时间判断
            if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_APP.getId())) {
                user.setChannel(JsonUtil.toString(tuple3.getT2()));
            } else {
                user.setChannel(channel);
            }
            userService.updateById(user);
            //添加渠道变更记录
            userAgentLogService.agentUpdateLog(oldAgentChannel, agent.getId(), channel);
            //回传用户信息
            callBackService.userCallBack(user, tuple3.getT2());
            //注册信息埋点数据发送到卡夫卡
            kafkaService.eventTrack(KafkaEventTrackEnum.KAFKA_EVENT_TRACK_AGENT_UPDATE, JsonUtil.toString(user));
        } catch (Exception e) {
            log.error("用户渠道更新异常, user : {}, userData : {}, e : {}", JsonUtil.toString(user),
                    JsonUtil.toString(userData), e.getMessage(), e);
        }
    }

    @Override
    public Tuple3<Long, Map<String, String>, String> getUserAgentId(Game game, UserData userData, String openId) {
        try {
            //微信小游戏解析
            if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_WX_APPLET.getId())) {
                Tuple3<Long, Map<String, String>, String> tuple3 = this.appletChannelTransform(userData.getChannel());
                //非自然量, 或者不存在openId, 直接返回结束
                if (!Objects.equals(tuple3.getT1(), 0L) || Strings.isBlank(openId)) {
                    return tuple3;
                }
                //自然量, 做监测链接匹配补偿归因
                Tuple2<Long, Map<String, String>> tuple2 = this.getChannelByOpenId(game.getId(), openId);
                if (tuple2 == null) {
                    return tuple3;
                }
                userData.setChannel(JsonUtil.toString(tuple2.second));
                return Tuples.of(tuple2.first, tuple2.second, tuple3.getT3());
            }
            //APP解析
            if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_APP.getId())) {
                return this.appChannelTransform(game, userData);
            }
            //H5解析
            if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_H5.getId())) {
                return this.h5ChannelTransform(userData.getChannel());
            }
        } catch (Exception e) {
            log.error("用户渠道解析异常, game : {}, userData : {}, e : {}", JsonUtil.toString(game),
                    JsonUtil.toString(userData), e.getMessage(), e);
        }
        //默认返回自然量
        return Tuples.of(0L, Collections.emptyMap(), Strings.EMPTY);
    }

    private Tuple2<Long, Map<String, String>> getChannelByOpenId(Long gameId, String openId) {
        try {
            Map<String, String> callBackMap = tencentMiniGameBackRpc.getCallBackByOpenId(gameId, openId).getData();
            if (CollectionUtils.isEmpty(callBackMap) || !callBackMap.containsKey("agentKey")) {
                return null;
            }
            Agent agent = super.getOne(new LambdaQueryWrapper<Agent>().eq(Agent::getAgentKey, callBackMap.get("agentKey")));
            if (agent == null) {
                return null;
            }
            Map<String, String> channelMap = new HashMap<>(3);
            callBackMap.put("weixinadinfo", callBackMap.get("adgroupId") + "." + callBackMap.get("impressionId"));
            callBackMap.put("gdt_vid", callBackMap.get("impressionId"));
            callBackMap.put("callBack", JsonUtil.toString(callBackMap));
            return Tuple2.with(agent.getId(), channelMap);
        } catch (Exception e) {
            log.error("微信小游戏补偿匹配监测链接异常, gameId : {}, openId : {}, e : {}", gameId, openId, e.getMessage(), e);
        }
        return null;
    }

    private Tuple3<Long, Map<String, String>, String> appletChannelTransform(String channel) {
        //链接参数
        if (Strings.isBlank(channel)) {
            return Tuples.of(0L, Collections.emptyMap(), Strings.EMPTY);
        }
        //数据解析
        Map<String, String> urlParamMap = this.channelTransform(channel);
        String shareUserId = null;
        String shareUserInfo = urlParamMap.get("shareUserId");
        if (Strings.isNotBlank(shareUserInfo)) {
            int questionMarkIndex = shareUserInfo.indexOf('?');
            shareUserId = questionMarkIndex == -1 ? shareUserInfo : shareUserInfo.substring(0, questionMarkIndex);
        }
        //查询渠道
        Agent agent = null;
        if (Strings.isNotBlank(shareUserId)) {
            User user = userService.getById(Long.valueOf(shareUserId));
            if (user != null) {
                agent = super.getById(user.getAgentId());
            }
        } else {
            agent = this.getAgentByKey(urlParamMap);
        }
        if (agent == null) {
            return Tuples.of(0L, Collections.emptyMap(), Strings.isBlank(shareUserId) ? Strings.EMPTY : shareUserId);
        }
        return Tuples.of(agent.getId(), urlParamMap, Strings.isBlank(shareUserId) ? Strings.EMPTY : shareUserId);
    }

    private Tuple3<Long, Map<String, String>, String> appChannelTransform(Game game, UserData userData) {
        //直投的APP, 必定兼容了媒体的回传sdk
        if (Strings.isNotBlank(game.getAdSdkConfig())) {
            //头条巨量引擎
            if (Objects.equals(game.getPutType(), 1)) {
                Agent agent = super.getOne(new LambdaQueryWrapper<Agent>().eq(Agent::getAgentKey, userData.getChannel()));
                return Tuples.of(agent == null ? 0 : agent.getId(), Collections.singletonMap("agentKey", userData.getChannel()), Strings.EMPTY);
            }
            //腾讯广点通
            if (Objects.equals(game.getPutType(), 2)) {
                Map<String, String> channelMap = JsonUtil.toMap(userData.getChannel(), Map.class, String.class);
                if (channelMap == null || !channelMap.containsKey("agentKey")) {
                    return Tuples.of(0L, Collections.emptyMap(), Strings.EMPTY);
                }
                Agent agent = super.getOne(new LambdaQueryWrapper<Agent>().eq(Agent::getAgentKey, channelMap.get("agentKey")));
                return Tuples.of(agent == null ? 0 : agent.getId(), channelMap, Strings.EMPTY);
            }
        }
        //头条私域, 虚拟游戏方式
        if (Objects.equals(game.getPutType(), 3)) {
            //查询访问记录
            UserVisitLog userVisitLog = this.getUserVisitLog(userData);
            if (userVisitLog == null) {
                //未匹配到访问信息, 返回自然量
                return Tuples.of(0L, Collections.emptyMap(), Strings.EMPTY);
            }
            //解析访问记录中得请求参数
            Map<String, String> urlParamMap = this.channelTransform(userVisitLog.getChannel());
            //查询渠道
            Agent agent = this.getAgentByKey(urlParamMap);
            //返回渠道信息
            return Tuples.of(agent == null ? 0L : agent.getId(), urlParamMap, Strings.EMPTY);
        }
        //腾讯私域, 直接加企微方式
        if (Objects.equals(game.getPutType(), 4)) {
            Tuple2<String, Long> tuple2 = this.queryUserAgentFromCallback(game, userData);
            Map<String, String> map = new HashMap<>(2);
            map.put("agentKey", tuple2.first);
            map.put("callbackId", tuple2.second.toString());
            Agent agent = super.getOne(new LambdaQueryWrapper<Agent>().eq(Agent::getAgentKey, tuple2.first));
            return Tuples.of(agent == null ? 0 : agent.getId(), map, Strings.EMPTY);
        }
        //不投放, 不回传, 只归因渠道, -- 不投放, 且回传只存在于导量情况, 无注册行为
        if (Objects.equals(game.getIsPut(), Boolean.FALSE)) {
            GameExt gameExt = gameExtService.getByGameId(userData.getGameId());
            if (gameExt != null && Objects.equals(gameExt.getAdCallBackSwitch(), Boolean.FALSE)) {
                //查询访问记录
                UserVisitLog userVisitLog = this.getUserVisitLog(userData);
                //返回渠道信息
                return Tuples.of(userVisitLog == null ? 0L : userVisitLog.getAgentId(), Collections.emptyMap(), Strings.EMPTY);
            }
        }
        //默认返回自然量
        return Tuples.of(0L, Collections.emptyMap(), Strings.EMPTY);
    }

    private Tuple3<Long, Map<String, String>, String> h5ChannelTransform(String channel) {
        //链接参数
        if (Strings.isBlank(channel)) {
            return Tuples.of(0L, Collections.emptyMap(), Strings.EMPTY);
        }
        //数据解析
        Map<String, String> urlParamMap = this.channelTransform(channel);
        //查询渠道
        Agent agent = this.getAgentByKey(urlParamMap);
        //返回
        return Tuples.of(agent == null ? 0L : agent.getId(), urlParamMap, Strings.EMPTY);
    }

    private Tuple2<String, Long> queryUserAgentFromCallback(Game game, UserData userData) {
        TencentAppApiUserAgentQueryRpcDTO tencentAppApiUserAgentQueryRpcDTO = TencentAppApiUserAgentQueryRpcDTO.builder()
                .gameId(game.getId())
                .regIp(userData.getIp())
                .registerTime(LocalDateTime.now())
                .imei(userData.getImei())
                .oaid(userData.getOaid())
                .androidId(userData.getAndroidId())
                .idfa(userData.getIdfa())
                .caid(userData.getCaid())
                .build();
        return tencentAppApiBackRpc.queryUserAgentFromCallback(tencentAppApiUserAgentQueryRpcDTO).getData();
    }

    private UserVisitLog getUserVisitLog(UserData userData) {
        //当前时间
        LocalDateTime regTime = LocalDateTime.now();
        // 0. 优先前置策略, 单独判断ip是否只有一个用户, 只有一个用户则是该用户
        List<UserVisitLog> userVisitLogList = userVisitLogService.list(new LambdaQueryWrapper<UserVisitLog>()
                .eq(UserVisitLog::getIp, userData.getIp()));
        if (CollectionUtils.isNotEmpty(userVisitLogList) && userVisitLogList.size() == 1) {
            return userVisitLogList.get(0);
        }
        // 1. 策略一 : 根据 ip + ua 精准匹配用户
        userVisitLogList = userVisitLogService.list(new LambdaQueryWrapper<UserVisitLog>()
                .eq(UserVisitLog::getIp, userData.getIp())
                .eq(UserVisitLog::getUa, userData.getUa())
                .le(UserVisitLog::getCreateTime, regTime));
        // 判断通过策略一是否拿到数据, 筛选出访问时间与注册时间最接近的
        if (CollectionUtils.isNotEmpty(userVisitLogList)) {
            return this.getLastUserCall(userVisitLogList, regTime);
        }
        // 2. 策略二 : 根据 ip + regTime 模糊匹配用户, 数据误差在15分钟以内
        userVisitLogList = userVisitLogService.list(new LambdaQueryWrapper<UserVisitLog>()
                .eq(UserVisitLog::getIp, userData.getIp())
                .ge(UserVisitLog::getCreateTime, regTime.minusMinutes(15))
                .le(UserVisitLog::getCreateTime, regTime));
        // 判断通过策略二是否拿到数据, 筛选出访问时间与注册时间最接近的
        if (CollectionUtils.isNotEmpty(userVisitLogList)) {
            return this.getLastUserCall(userVisitLogList, regTime);
        }
        // 3. 策略三 : 根据 ua + regTime 模糊匹配用户, 数据误差在15分钟以内
        userVisitLogList = userVisitLogService.list(new LambdaQueryWrapper<UserVisitLog>()
                .eq(UserVisitLog::getUa, userData.getUa())
                .ge(UserVisitLog::getCreateTime, regTime.minusMinutes(15))
                .le(UserVisitLog::getCreateTime, regTime));
        // 判断通过策略三是否拿到数据, 筛选出访问时间与注册时间最接近的
        if (CollectionUtils.isNotEmpty(userVisitLogList)) {
            return this.getLastUserCall(userVisitLogList, regTime);
        }
        // 4. 策略四 : 根据 regTime 模糊匹配用户, 数据误差在15分钟以内, 此策略为兜底策略
        userVisitLogList = userVisitLogService.list(new LambdaQueryWrapper<UserVisitLog>()
                .ge(UserVisitLog::getCreateTime, regTime.minusMinutes(15))
                .le(UserVisitLog::getCreateTime, regTime));
        if (CollectionUtils.isNotEmpty(userVisitLogList)) {
            return this.getLastUserCall(userVisitLogList, regTime);
        }
        return null;
    }

    /**
     * 对比获取访问时间与注册时间最接近的访问记录
     *
     * @param regTime          用户注册时间
     * @param userVisitLogList 用户访问日志列表
     * @return {@link UserVisitLog}
     */
    private UserVisitLog getLastUserCall(List<UserVisitLog> userVisitLogList, LocalDateTime regTime) {
        //只匹配到一个用户, 直接返回
        if (userVisitLogList.size() == 1) {
            return userVisitLogList.get(0);
        }
        //注册时间转换毫秒
        long regTimeMill = DateUtil.localDateTimeToMilli(regTime);
        //用户访问时间与注册时间差
        Map<Long, Long> timeDiffMap = new HashMap<>(userVisitLogList.size());
        //循环用户列表
        userVisitLogList.forEach(user -> {
            //用户访问时间
            LocalDateTime createTime = user.getCreateTime();
            //用户访问时间转换
            long createTimeMill = DateUtil.localDateTimeToMilli(createTime);
            //计算时间差, 并添加到集合
            timeDiffMap.put(user.getId(), regTimeMill - createTimeMill);
        });
        //取出map中最小的value值对应的key值
        List<Map.Entry<Long, Long>> list = new ArrayList<>(timeDiffMap.entrySet());
        list.sort(Comparator.comparingLong(Map.Entry::getValue));
        Long minId = list.get(0).getKey();
        //过滤出对应的对象返回
        return userVisitLogList.stream().filter(user -> Objects.equals(user.getId(), minId)).findFirst().orElse(null);
    }

    @Override
    public Agent getAgentByKey(Map<String, String> urlParamMap) {
        //参数不存在
        if (CollectionUtils.isEmpty(urlParamMap)) {
            return null;
        }
        //判断渠道标识
        String state = urlParamMap.get("agentKey");
        if (Strings.isBlank(state)) {
            state = urlParamMap.get("state");
        }
        return super.getOne(new LambdaQueryWrapper<Agent>().eq(Agent::getAgentKey, state));
    }

    @Override
    public Map<String, String> channelTransform(String channel) {
        if (Strings.isBlank(channel) || Strings.isBlank(channel.trim())) {
            return Collections.emptyMap();
        }
        //渠道参数
        Map<String, String> paramMap = this.channelToMap(channel);
        //企业微信拼接字段名
        String callState = "callState";
        //判断是否包含转译参数
        if (!paramMap.containsKey(callState)) {
            return paramMap;
        }
        //小程序访问信息
        UserApplet userApplet = userAppletService.getById(Integer.valueOf(paramMap.get(callState)));
        if (userApplet != null && Strings.isNotBlank(userApplet.getChannel())) {
            paramMap.putAll(this.channelToMap(userApplet.getChannel()));
            paramMap.put("appId", userApplet.getAppId());
            paramMap.put("openId", userApplet.getOpenId());
        }
        return paramMap;
    }

    private Map<String, String> channelToMap(String channel) {
        if (Strings.isBlank(channel) || Strings.isBlank(channel.trim())) {
            return Collections.emptyMap();
        }
        //判断是否是json, 是就直接转成map
        if (this.isJsonStr(channel)) {
            return JsonUtil.toMap(channel, Map.class, String.class);
        }
        //非json, 自己解析
        Map<String, String> paramMap = new HashMap<>(11);
        String[] params = channel.split("&");
        for (String param : params) {
            String[] keyValue = param.split("=");
            if (keyValue.length > 1) {
                String key = keyValue[0];
                String value = keyValue[1];
                paramMap.put(key, value);
            }
        }
        return paramMap;
    }

    @Override
    public Agent getAgentByChannel(String channel) {
        Map<String, String> urlParamMap = this.channelTransform(channel);
        return this.getAgentByKey(urlParamMap);
    }

    private boolean isJsonStr(String str) {
        if (Strings.isBlank(str) || Strings.isBlank(str.trim())) {
            return false;
        }
        boolean result = false;
        try {
            JSON.parse(str);
            result = true;
        } catch (Exception ignored) {
        }
        return result;
    }
}
