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

import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
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.zanxiang.erp.base.ErpServer;
import com.zanxiang.erp.base.rpc.IDingTalkMsgRpc;
import com.zanxiang.game.module.base.ServerInfo;
import com.zanxiang.game.module.base.pojo.enums.PayApplicationTypeEnum;
import com.zanxiang.game.module.base.pojo.enums.StatusEnum;
import com.zanxiang.game.module.base.rpc.IWxApiServiceRpc;
import com.zanxiang.game.module.manage.constant.RedisKeyConstant;
import com.zanxiang.game.module.manage.enums.ExpireTimeEnum;
import com.zanxiang.game.module.manage.service.*;
import com.zanxiang.game.module.manage.utils.RedisUtil;
import com.zanxiang.game.module.mybatis.entity.GamePayWay;
import com.zanxiang.game.module.mybatis.entity.ListenCall;
import com.zanxiang.game.module.mybatis.entity.PayApplication;
import com.zanxiang.game.module.mybatis.entity.PayBox;
import com.zanxiang.module.redis.service.IDistributedLockComponent;
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.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author : lingfeng
 * @time : 2024-01-20
 * @description : 小程序检查
 */
@Slf4j
@Service
public class AppletCheckServiceImpl implements IAppletCheckService {

    @DubboReference(providedBy = ErpServer.SERVER_DUBBO_NAME)
    private IDingTalkMsgRpc dingTalkMsgRpc;

    @DubboReference(providedBy = ServerInfo.SERVER_SDK_DUBBO_NAME)
    private IWxApiServiceRpc wxApiServiceRpc;

    @Autowired
    private RedisUtil<Integer> redisUtil;

    @Autowired
    private IPayBoxService payBoxService;

    @Autowired
    private IGamePayWayService gamePayWayService;

    @Autowired
    private IListenCallService listenCallService;

    @Autowired
    private IPayApplicationService payApplicationService;

    @Autowired
    private IDistributedLockComponent distributedLockComponent;

    @Override
    public void payApplicationCheck() {
        List<PayApplication> payApplicationList = payApplicationService.list(new LambdaQueryWrapper<PayApplication>()
                .eq(PayApplication::getStatus, StatusEnum.YES.getCode())
                .eq(PayApplication::getType, PayApplicationTypeEnum.WX_MINI_APP.getType()));
        if (CollectionUtils.isEmpty(payApplicationList)) {
            return;
        }
        payApplicationList.forEach(payApplication -> {
            String accessToken = null;
            try {
                accessToken = wxApiServiceRpc.getAccessToken(payApplication.getAppId(), payApplication.getAppSecret());
            } catch (Exception e) {
                log.error("获取小程序token异常, appName : {}, e : {}", payApplication.getAppName(), e.getMessage());
            }
            if (Strings.isBlank(accessToken) || !this.appletCheck(accessToken, payApplication.getAppName())) {
                log.error("小程序判定封停或者禁用, appName : {}, accessToken : {}", payApplication.getAppName(), accessToken);
                this.payApplicationCheck(payApplication);
            }
        });
    }

    @Override
    public void payApplicationCheck(PayApplication payApplication) {
        //异常计数器, 返回false的时候过滤
        if (!this.countUpdate(payApplication.getAppId())) {
            return;
        }
        //连续三次报警, 进行通知
        try {
            //更新游戏支付
            this.gamePayUpdate(payApplication);
        } catch (Exception e) {
            log.error("支付应用异常更新游戏支付盒子失败, appName : {}, e : {}", payApplication.getAppName(), e.getMessage());
        }
        try {
            //钉钉通知
            this.sendDingTalkMsg(payApplication);
        } catch (Exception e) {
            log.error("支付应用异常钉钉通知失败, appName : {}, e : {}", payApplication.getAppName(), e.getMessage());
        }
        try {
            //电话通知
            this.sendPhoneTalkMsg(payApplication);
        } catch (Exception e) {
            log.error("支付应用异常电话通知失败, appName : {}, e : {}", payApplication.getAppName(), e.getMessage());
        }
    }

    private boolean countUpdate(String appId) {
        String key = RedisKeyConstant.APPLET_ERROR_COUNT + appId;
        Integer count = redisUtil.getCache(key);
        if (count != null && count >= 2) {
            redisUtil.deleteCache(key);
            return Boolean.TRUE;
        }
        if (count == null) {
            count = 1;
        } else {
            count += 1;
        }
        redisUtil.setCache(key, count, ExpireTimeEnum.HALF_HOUR.getTime());
        return Boolean.FALSE;
    }

    private void gamePayUpdate(PayApplication payApplication) {
        //修改支付应用状态
        log.error("支付应用异常 - 修改支付应用状态, appName : {}", payApplication.getAppName());
        payApplicationService.update(new LambdaUpdateWrapper<PayApplication>()
                .eq(PayApplication::getAppId, payApplication.getAppId())
                .set(PayApplication::getStatus, StatusEnum.NO.getCode())
                .set(PayApplication::getUpdateTime, LocalDateTime.now()));
        //查询支付应用对应的盒子
        List<PayBox> payBoxList = payBoxService.list(new LambdaQueryWrapper<PayBox>()
                .eq(PayBox::getAppId, payApplication.getAppId())
                .eq(PayBox::getStatus, StatusEnum.YES.getCode()));
        if (CollectionUtils.isEmpty(payBoxList)) {
            log.error("支付应用异常 - 支付应用不存在相关支付盒子, appName : {}", payApplication.getAppName());
            return;
        }
        Set<Integer> payBoxIdList = payBoxList.stream().map(PayBox::getId).collect(Collectors.toSet());
        //修改支付应用相关支付盒子状态
        log.error("支付应用异常 - 修改支付盒子状态, appName : {}, payBoxIdList : {}", payApplication.getAppName(), payBoxIdList);
        payBoxService.update(new LambdaUpdateWrapper<PayBox>()
                .in(PayBox::getId, payBoxIdList)
                .set(PayBox::getStatus, StatusEnum.NO.getCode())
                .set(PayBox::getUpdateTime, LocalDateTime.now()));
        //游戏支付应用修改
//        this.gamePayWayUpdate(payApplication, payBoxIdList);
    }

    private void gamePayWayUpdate(PayApplication payApplication, Set<Integer> payBoxIdList) {
        //查询相关盒子被使用到的游戏
        List<GamePayWay> gamePayWayList = gamePayWayService.list(new LambdaQueryWrapper<GamePayWay>()
                .in(GamePayWay::getPayBoxId, payBoxIdList));
        if (CollectionUtils.isEmpty(gamePayWayList)) {
            log.error("支付应用异常 - 支付应用不存在相关联游戏支付配置, appName : {}", payApplication.getAppName());
            return;
        }
        //获取一个能用的支付盒子
        PayBox payBox = payBoxService.getOne(new LambdaQueryWrapper<PayBox>()
                .eq(PayBox::getType, payApplication.getType())
                .eq(PayBox::getStatus, StatusEnum.YES.getCode())
                .last("limit 1"));
        if (payBox == null) {
            log.error("支付应用异常 - 不存在正常可用的支付盒子, appName : {}", payApplication.getAppName());
            return;
        }
        //相关游戏支付配置id列表
        Set<Long> gamePayWayIdSet = gamePayWayList.stream().map(GamePayWay::getId).collect(Collectors.toSet());
        //修改游戏支付配置
        log.error("支付应用异常 - 修改游戏支付配置, appName : {}, gamePayWayIdSet : {}, payBoxId : {}", payApplication.getAppName(),
                gamePayWayIdSet, payBox.getId());
        gamePayWayService.update(new LambdaUpdateWrapper<GamePayWay>()
                .in(GamePayWay::getId, gamePayWayIdSet)
                .set(GamePayWay::getPayBoxId, payBox.getId())
                .set(GamePayWay::getUpdateTime, LocalDateTime.now()));
    }

    private void sendDingTalkMsg(PayApplication payApplication) {
        List<ListenCall> listenCallList = this.callListenUser();
        if (CollectionUtils.isEmpty(listenCallList)) {
            return;
        }
        String content = System.currentTimeMillis() + " 小程序 : <" + payApplication.getAppName() + "> 可能被封停或者禁用, 请注意验证";
        Set<Long> userIdSet = listenCallList.stream().map(ListenCall::getUserId).collect(Collectors.toSet());
        userIdSet.forEach(userId -> dingTalkMsgRpc.sendByUserId(userId, content));
    }

    private void sendPhoneTalkMsg(PayApplication payApplication) {
        List<ListenCall> listenCallList = this.callListenUser();
        if (CollectionUtils.isEmpty(listenCallList)) {
            return;
        }
        String content = payApplication.getAppName() + "监测异常";
        Set<String> phoneNumSet = listenCallList.stream().map(ListenCall::getPhoneNum).collect(Collectors.toSet());
        phoneNumSet.forEach(phoneNum -> {
            String lockKey = RedisKeyConstant.PHONE_CALL_LOCK + phoneNum;
            if (!distributedLockComponent.doLock(lockKey, 0L, 1L, TimeUnit.MINUTES)) {
                return;
            }
            this.phoneCall(content, phoneNum);
        });
    }

    private List<ListenCall> callListenUser() {
        List<ListenCall> listenCallList = listenCallService.list(new LambdaQueryWrapper<ListenCall>()
                .eq(ListenCall::getStatus, StatusEnum.YES.getCode()));
        if (CollectionUtils.isEmpty(listenCallList)) {
            return Collections.emptyList();
        }
        Collection<ListenCall> collection = listenCallList.stream()
                .collect(Collectors.toMap(ListenCall::getUserId, Function.identity(), (existing, replacement) -> existing))
                .values();
        return new ArrayList<>(collection);
    }

    private boolean appletCheck(String token, String appName) {
        //参数对象
        String dayTime = LocalDate.now().minusDays(3).toString();
        Map<String, String> paramMap = new HashMap<>(2);
        paramMap.put("begin_date", dayTime);
        paramMap.put("end_date", dayTime);
        String host = "https://api.weixin.qq.com/datacube/getweanalysisappiddailyretaininfo?access_token=" + token;
        try {
            // 带参POST请求
            String result = HttpUtil.post(host, JsonUtil.toString(paramMap));
            //结果为空, 判定腾讯接口返回错误, 不判定为封禁
            if (Strings.isBlank(result)) {
                return Boolean.TRUE;
            }
            //没有报错
            if (!result.contains("errcode")) {
                return Boolean.TRUE;
            }
            //结果返回错误
            Map<String, Object> resultMap = JsonUtil.toMap(result, Map.class, Object.class);
            //排除计算中的特殊报错
            if (Objects.equals(resultMap.get("errcode").toString(), "61503")) {
                return Boolean.TRUE;
            }
            log.error("小程序封停监测结果, appName : {}, result : {}", appName, result);
        } catch (Exception e) {
            log.error("小程序封停监测异常, appName : {},  e : {}", appName, e.getMessage());
            return Boolean.FALSE;
        }
        return Boolean.FALSE;
    }

    private void phoneCall(String param, String mobile) {
        RestTemplate restTemplate = new RestTemplate();
        String appCode = "f395b1587fc04a49a975f908660fb1e9";
        String host = "https://jumfixed.market.alicloudapi.com/voice-notify/send";
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "APPCODE " + appCode);
        headers.set("Content-Type", ContentType.FORM_URLENCODED.getValue());
        //参数
        MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
        requestParams.add("mobile", mobile);
        requestParams.add("templateId", "JMJNAWUQOJP9");
        requestParams.add("param", param);
        String result = null;
        try {
            ResponseEntity<String> responseEntity = restTemplate.exchange(host, HttpMethod.POST,
                    new HttpEntity<>(requestParams, headers), String.class);
            result = responseEntity.getBody();
        } catch (Exception e) {
            log.error("阿里语音呼叫失败, requestParams : {}, e : {}", JsonUtil.toString(requestParams), e.getMessage());
        }
        log.error("阿里语音呼叫结果, result : {}", result);
    }

}
