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.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.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.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 {

    @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;

    @Override
    public void userAgentUpdate(User user, String channel) {
        log.error("用户登录接收到的渠道参数 userId : {}, channel : {}", user.getId(), channel);
        try {
            //渠道参数为空
            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天未登录, 更新渠道
            Map<String, String> urlParamMap = this.channelTransform(channel);
            //查询渠道
            Agent agent = this.getAgentByKey(urlParamMap);
            if (agent == null) {
                return;
            }
            //更新用户信息
            userService.update(new LambdaUpdateWrapper<User>()
                    .set(User::getAgentId, agent.getId())
                    .set(User::getChannel, channel)
                    .set(User::getUpdateTime, LocalDateTime.now())
                    .eq(User::getId, user.getId()));
            //添加渠道变更记录
            userAgentLogService.agentUpdateLog(user, agent.getId(), channel);
            //回传用户信息
            user.setAgentId(agent.getId());
            user.setChannel(channel);
            callBackService.userCallBack(user, urlParamMap);
        } catch (Exception e) {
            log.error("用户渠道更新异常, userId : {}, channel : {}, e : {}", user.getId(), channel, e.getMessage());
        }
    }

    @Override
    public Tuple3<Long, Map<String, String>, String> getUserAgentId(UserData userData) {
        log.error("用户注册接收到的渠道参数 userData : {}", JsonUtil.toString(userData));
        //获取游戏信息
        GameExt gameExt = gameExtService.getByGameId(userData.getGameId());
        Game game = gameService.getById(userData.getGameId());
        //不投放得APP游戏按ip和ua进行渠道匹配
        if (Objects.equals(game.getCategory(), GameCategoryEnum.CATEGORY_APP.getId())
                && !Objects.equals(gameExt.getAdCallBackSwitch(), Boolean.TRUE)) {
            UserVisitLog userVisitLog = this.getUserVisitLog(userData);
            if (userVisitLog == null) {
                return Tuples.of(0L, Collections.emptyMap(), Strings.EMPTY);
            }
            return Tuples.of(userVisitLog.getAgentId(), Collections.emptyMap(), Strings.EMPTY);
        }
        //链接参数
        if (Strings.isBlank(userData.getChannel())) {
            return Tuples.of(0L, Collections.emptyMap(), Strings.EMPTY);
        }
        //数据解析
        Map<String, String> urlParamMap = this.channelTransform(userData.getChannel());
        String shareUserId = urlParamMap.get("shareUserId");
        //查询渠道
        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 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)
                .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);
    }

    private 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();
        }
        //判断是否是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;
    }
}
