package com.duiba.tuia.youtui.web.service.impl;/**
 * Created by chengdeman .
 * 17/8/16 .
 */

import cn.com.duiba.tuia.activity.center.api.constant.BalanceRecordType;
import cn.com.duiba.tuia.activity.center.api.constant.BalanceType;
import cn.com.duiba.tuia.activity.center.api.dto.consumer.req.BalanceRecordAdd;
import cn.com.duiba.tuia.activity.center.api.remoteservice.RemoteUserBalanceService;
import cn.com.duiba.tuia.activity.center.api.util.MathUtil;
import cn.com.duiba.tuia.ssp.center.api.dto.MediaAppDataDto;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.dianping.cat.Cat;
import com.duiba.tuia.youtui.web.constant.CacheKey;
import com.duiba.tuia.youtui.web.constant.Constants;
import com.duiba.tuia.youtui.web.constant.DeliveryType;
import com.duiba.tuia.youtui.web.constant.ErrorCode;
import com.duiba.tuia.youtui.web.exception.ActivityException;
import com.duiba.tuia.youtui.web.log.InnerLogService;
import com.duiba.tuia.youtui.web.model.req.AdvertEffectReq;
import com.duiba.tuia.youtui.web.model.req.TAdclickReq;
import com.duiba.tuia.youtui.web.model.req.TclickReq;
import com.duiba.tuia.youtui.web.model.req.TdislikeReq;
import com.duiba.tuia.youtui.web.model.toutiao.Adslot;
import com.duiba.tuia.youtui.web.model.toutiao.TouTiaoBidRequest;
import com.duiba.tuia.youtui.web.model.toutiao.TouTiaoData;
import com.duiba.tuia.youtui.web.service.AppService;
import com.duiba.tuia.youtui.web.service.BaseCacheService;
import com.duiba.tuia.youtui.web.service.ThirPartyService;
import com.duiba.tuia.youtui.web.tongdun.FraudApiInvoker;
import com.duiba.tuia.youtui.web.tool.CachedKeyUtils;
import com.duiba.tuia.youtui.web.tool.HttpRequestTool;
import com.duiba.tuia.youtui.web.tool.JsonUtils;
import com.duiba.tuia.youtui.web.tool.MD5;
import com.duiba.tuia.youtui.web.tool.RequestLocal;
import com.duiba.tuia.youtui.web.tool.RequestTool;
import com.duiba.tuia.youtui.web.tool.SHA1;
import com.duiba.tuia.youtui.web.tool.http.HttpAsyncClientPool;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;

import static com.duiba.tuia.youtui.web.constant.ErrorCode.E0110006;

/**
 * 第三方接口service实现类
 *
 * @author chengdeman
 * @create 2017-08-16 下午2:43
 **/
@Service
public class ThirPartyServiceImpl  extends BaseCacheService implements ThirPartyService {

    /**
     * The Constant log.
     */
    private static final Logger log = LoggerFactory.getLogger(ThirPartyServiceImpl.class);

    @Autowired
    private FraudApiInvoker fraudApiInvoker;

    private LoadingCache<String, Boolean> visitLimitCache;

    @Autowired
    private HttpAsyncClientPool httpAsyncClientPool;

    @Autowired
    private AppService                    appService;
    @Autowired
    private RemoteUserBalanceService      remoteUserBalanceService;

    public static final String RECHARGE_SUCCESS = "0";
    public static final String RECHARGE_FAILED = "3";

    private ThirPartyServiceImpl() {
        visitLimitCache = CacheBuilder
                .newBuilder()
                .refreshAfterWrite(2, TimeUnit.MINUTES)
                .concurrencyLevel(4)
                .initialCapacity(100)
                .maximumSize(1000000L)
                .build(new CacheLoader<String, Boolean>() {
                    @Override
                    public Boolean load(String s) throws Exception {
                        return false;
                    }
                });
    }


    /**
     * 获取头条信息
     *
     * @param request
     * @return
     * @throws ActivityException
     */
    @Override
    public String getTdata(HttpServletRequest request, String category, String refreshTime) throws ActivityException {
        TouTiaoData touTiaoData = getTouTiaoData(request, category, refreshTime);
        return touTiaoData == null ? null : touTiaoData.getData();
    }

    private TouTiaoData getTouTiaoData(HttpServletRequest request, String category, String refreshTime) throws ActivityException {
        TouTiaoData touTiaoData = new TouTiaoData();
        //  1.获取请求token信息,组装签名，token等请求参数
        Long consumerId = RequestLocal.get().getCid();
        Long timestamp = new Date().getTime();
        TreeMap<String, Object> parameterMap = getTtockenParam(consumerId, timestamp);

        if (parameterMap == null) {
            throw new ActivityException(ErrorCode.E9999999.geteCode(), Constants.NETWORK_ERROR);
        }

        //2.根据token获取文章列表信息
        parameterMap.put("category", category);
        parameterMap.put("min_behot _time", refreshTime == null ? (timestamp - 10) : refreshTime);
        parameterMap.put("max_behot _time", timestamp);
        parameterMap.put("Ip", RequestTool.getIpAddr(request));
        parameterMap.put("language", "simplified");

        // 不同的系统传入不同的代码位
        if ("Android".equals(RequestTool.getOS(request))) {
            parameterMap.put("os", RequestTool.getOS(request));
            parameterMap.put("union_rit", Constants.THIRDVARIABLE.TOUTIAO_ANDROID_UNION_RIT);
        } else if ("IOS".equals(RequestTool.getOS(request))) {
            parameterMap.put("os", RequestTool.getOS(request));
            parameterMap.put("union_rit", Constants.THIRDVARIABLE.TOUTIAO_IOS_UNION_RIT);
        } else {
            parameterMap.put("os", "Android");
            parameterMap.put("union_rit", Constants.THIRDVARIABLE.TOUTIAO_ANDROID_UNION_RIT);
        }

        parameterMap.put("https", 1);
        try {
            String strdata = HttpRequestTool.requestByPost(Constants.CUSTOM.TOUTIAO_DATA_INFO_HTTP_DOMAIN, parameterMap);
            JSONObject dataStr = JSONObject.parseObject(strdata);
            String msg = dataStr.getString("msg");
            if (null != msg && "success".equals(msg)) {
                String data = dataStr.getString("data");
                touTiaoData.setData(data);
                touTiaoData.setAccessToken(parameterMap.get("access_token").toString());
                touTiaoData.setTimestamp(parameterMap.get("timestamp").toString());
                touTiaoData.setNonce(parameterMap.get("nonce").toString());
                touTiaoData.setPartner(parameterMap.get("partner").toString());
                touTiaoData.setSignature(parameterMap.get("signature").toString());
                return touTiaoData;
            }
        } catch (Exception e) {
            log.warn(" get 今日头条 data Exception ,ip = " + RequestLocal.get().getIp() + " ; aapId = " + RequestLocal.get().getAppId() + "; request param = " + JSONObject.toJSONString(parameterMap), e);
        }
        return null;
    }

    @Override
    public String getTsearch(HttpServletRequest request, String keyword) throws ActivityException {

        //  1.获取请求token信息,组装签名，token等请求参数
        Long consumerId = RequestLocal.get().getCid();
        Long timestamp = new Date().getTime();
        TreeMap<String, Object> parameterMap = getTtockenParam(consumerId, timestamp);

        if (parameterMap == null) {
            throw new ActivityException(ErrorCode.E9999999.geteCode(), Constants.NETWORK_ERROR);
        }

        parameterMap.put("keyword", keyword);
        parameterMap.put("offset", 0);
        parameterMap.put("count", 10);
        String url = Constants.CUSTOM.TOUTIAO_SEARCH_HTTP_DOMAIN + "?" + getUrlParamsByMap(parameterMap);
        String strdata = HttpRequestTool.requestByGet(url);

        JSONObject dataStr = JSONObject.parseObject(strdata);
        String msg = dataStr.getString("msg");
        if (null != msg && "success".equals(msg)) {
            String data = dataStr.getString("data");
            return data;
        }
        return null;
    }

    @Override
    public String getTclick(HttpServletRequest request, TclickReq req) throws ActivityException {

        //  1.获取请求token信息,组装签名，token等请求参数
        Long consumerId = RequestLocal.get().getCid();
        Long timestamp = new Date().getTime();
        TreeMap<String, Object> parameterMap = getTtockenParam(consumerId, timestamp);

        if (parameterMap == null) {
            throw new ActivityException(ErrorCode.E9999999.geteCode(), Constants.NETWORK_ERROR);
        }

        //2.根据token获取文章列表信息
        String timestampStr = String.valueOf(timestamp / 1000);
        parameterMap.put("group_id", req.getGroup_id());
        parameterMap.put("type", req.getType());
        parameterMap.put("category", req.getCategory());
        parameterMap.put("client_at", timestampStr);
        parameterMap.put("page_id", req.getPageId());
        parameterMap.put("consumerId",consumerId);
        parameterMap.put("appId",RequestLocal.get().getAppId());
        //新闻点击日志
        InnerLogService.tNewsLog(parameterMap, new JSONObject());
        return "ok";
    }


    @Override
    public String getTAdclick(HttpServletRequest request, TAdclickReq req) throws ActivityException {
        //  1.获取请求token信息,组装签名，token等请求参数
        Long consumerId = RequestLocal.get().getCid();
        Long timestamp = new Date().getTime();
        String device_type = "IOS".equals(RequestTool.getUA(RequestTool.getUserAgent(request))) ? "Iphone" : "Android";
        TreeMap<String, Object> parameterMap = getTtockenParam(consumerId, timestamp);

        if (parameterMap == null) {
            throw new ActivityException(ErrorCode.E9999999.geteCode(), Constants.NETWORK_ERROR);
        }

        //2.根据token获取文章列表信息
        String times = String.valueOf(timestamp / 1000);
        Long show_time = (req.getShow_time() == null || req.getShow_time() > 3600000) ? 6000 : req.getShow_time();
        parameterMap.put("os", RequestTool.getOSNew(request));
        parameterMap.put("language", "simplified");

        TreeMap<String, Object> eventMap = new TreeMap<>();
        eventMap.put("category", req.getCategory());
        eventMap.put("tag", "embeded_ad");
        eventMap.put("is_ad_event", "1");

        eventMap.put("label", req.getLabel());
        eventMap.put("value", req.getValue());
        eventMap.put("log_extra", req.getLog_extra());

        eventMap.put("client_at", Integer.valueOf(times));
        eventMap.put("show_time", show_time);
        eventMap.put("client_ip", req.getClient_ip());

        parameterMap.put("events", JSON.toJSONString(eventMap));
        parameterMap.put("category", req.getT_category());
        parameterMap.put("group_id", req.getGroup_id());
        parameterMap.put("type", req.getType());
        parameterMap.put("type", "ad");
        parameterMap.put("client_at", Integer.valueOf(times));


        parameterMap.put("ad_id", req.getValue());
        parameterMap.put("label", req.getLabel());
        parameterMap.put("tag", "embeded_ad");
        parameterMap.put("is_ad_event", "1");
        parameterMap.put("log_extra", req.getLog_extra());
        parameterMap.put("show_time", show_time);
        parameterMap.put("client_ip", req.getClient_ip());
        parameterMap.put("device_type", device_type);
        parameterMap.put("pdid", consumerId);
        parameterMap.put("os_version", "5.0");


        // 3.异步上报信息
        fraudApiInvoker.invokeTouTiao(Constants.CUSTOM.TOUTIAO_AD_EVENT_UP_HTTP_DOMAIN, parameterMap, consumerId, RequestLocal.get().getAppId());


        return "ok";
    }

    @Override
    public String getTdislike(HttpServletRequest request, TdislikeReq req) throws ActivityException {

        //  1.获取请求token信息,组装签名，token等请求参数
        Long consumerId = RequestLocal.get().getCid();
        Long timestamp = new Date().getTime();
        TreeMap<String, Object> parameterMap = getTtockenParam(consumerId, timestamp);

        if (parameterMap == null) {
            throw new ActivityException(ErrorCode.E9999999.geteCode(), Constants.NETWORK_ERROR);
        }

        //2.根据token获取文章列表信息
        parameterMap.put("action", req.getAction());
        parameterMap.put("type", req.getType());
        parameterMap.put("id", req.getId());
        parameterMap.put("timestamp", timestamp);
        parameterMap.put("ad_id", req.getAd_id());
        parameterMap.put("item_id", req.getItem_id());
        parameterMap.put("filter_words", req.getFilter_words());
        parameterMap.put("ad_extra", req.getAd_extra());

        // 3.异步上报信息
        fraudApiInvoker.invokeTouTiao(Constants.CUSTOM.TOUTIAO_DIS_LIKE_HTTP_DOMAIN, parameterMap, consumerId, RequestLocal.get().getAppId());

        return "ok";
    }


    /**
     * 获取今日头条的token
     *
     * @return :  a
     * @Description :
     * @author :  chengdeman
     * @Date :  17/8/17
     */
    private TreeMap<String, Object> getTtockenParam(Long consumerId, Long timestamp) throws ActivityException {
        // 参数判断
        if (consumerId == null || timestamp == null) {
            throw new ActivityException(ErrorCode.E9999999.geteCode(), Constants.NETWORK_ERROR);
        }

        // 2.构建获取token必填的时间戳和随机数
        int nonce = ThreadLocalRandom.current().nextInt(1000);

        // 3.对随机数，时间戳和Secure key进行字典排序
        ArrayList<String> list = new ArrayList<>();
        list.add(String.valueOf(nonce));
        list.add(String.valueOf(timestamp));
        list.add(String.valueOf(Constants.THIRDVARIABLE.TOUTIAO_SECURE_KEY));
        Collections.sort(list);

        // 4.获取token参数进行sha1加密
        String signStr = list.get(0) + list.get(1) + list.get(2);

        String sigin = null;
        try {
            sigin = SHA1.sha1(signStr);
        } catch (Exception e) {
            return null;
        }

        // 5.构建获取token请求需要传的参数
        TreeMap<String, Object> parameterMap = new TreeMap<>();
        parameterMap.put("timestamp", timestamp);
        parameterMap.put("nonce", nonce);
        parameterMap.put("partner", Constants.THIRDVARIABLE.TOUTIAO_PARTNER);
        parameterMap.put("signature", sigin);
        parameterMap.put("uuid", consumerId);

        // 6.发起请求
        // 1. 根据consumerId  从缓存中获取token，获取不到在请求
        String token = null;
        try {
            token = getTtckenCache(consumerId);
        } catch (Exception e) {
            log.error("redis get  头条  token  异常");
        }

        try {
            if (StringUtils.isBlank(token)) {
                String str = HttpRequestTool.requestByPost(Constants.CUSTOM.TOUTIAO_TOKEN_HTTP_DOMAIN, parameterMap);

                if (str == null) {
                    return null;
                }
                JSONObject jsStr = JSONObject.parseObject(str);
                String msg = jsStr.containsKey("msg") ? jsStr.getString("msg") : null;
                if (null != msg && "success".equals(msg)) {
                    String data = jsStr.getString("data");
                    JSONObject dataStr = JSONObject.parseObject(data);
                    String accessToken = dataStr.getString("access_token");
                    // 7.将获取到的token放入缓存
                    setTtckenCache(consumerId, accessToken, dataStr.getLong("expires_in"));

                    // 8.赋值token
                    token = accessToken;
                }
            }
            parameterMap.put("access_token", token);

            return parameterMap;
        } catch (Exception e) {
            log.warn(" 今日头条获取token失败 Exception ,ip = " + RequestLocal.get().getIp() + " ; aapId = " + RequestLocal.get().getAppId() + "; request param = " + JSONObject.toJSONString(parameterMap), e);
        }
        return null;
    }


    /**
     * 根据用户ID为key获取缓存中的token
     *
     * @param consumerId 用户ID
     * @return
     * @throws ActivityException
     */
    private String getTtckenCache(Long consumerId) {
        String key = CachedKeyUtils.getRedisKey(CacheKey.TOUTIAO_TOKEN_CACHE, consumerId);
        Object o = stringRedisTemplate.opsForValue().get(key);
        if (o != null) {
            return (String) o;
        }
        return null;
    }

    /**
     * 用户ID为key，将今日头条获取的token，放入redis
     *
     * @param consumerId 用户ID
     * @param expireTime 失效时间
     * @return
     * @throws ActivityException
     */
    public void setTtckenCache(Long consumerId, String token, Long expireTime) {
        String key = CachedKeyUtils.getRedisKey(CacheKey.TOUTIAO_TOKEN_CACHE, consumerId);
        stringRedisTemplate.opsForValue().set(key, token);
        stringRedisTemplate.expire(key, 600, TimeUnit.SECONDS);
    }

    /**
     * 将map转换成url
     *
     * @param map
     * @return
     */
    private static String getUrlParamsByMap(Map<String, Object> map) {
        return SHA1.getUrlParamsByMap(map);
    }


    /**
     * 广告转化效果上报
     *
     * @param request
     * @param advertEffectReq 广告转化传入对象
     * @return
     * @throws ActivityException
     */
    @Override
    public JSONObject advertEffect(HttpServletRequest request, AdvertEffectReq advertEffectReq) throws ActivityException {
        JSONObject jsonObject = new JSONObject();
        // 1.参数非空判断
        // 2.判断是否已经回传过（a_tuiaid对比，1h内只记录一条，统计时也要做数据清洗，tuiaid重复的剔除）
        // 3.判断是否签名正确
        if (checkAdvertEffectParam(jsonObject, advertEffectReq) &&
                checkSignature(jsonObject, advertEffectReq)) {
            // 4.记录日志
        }
        return jsonObject;
    }


    /**
     * 校验广告回传必填参数
     *
     * @param jsonObject
     * @param advertEffectReq
     * @return
     */
    private Boolean checkAdvertEffectParam(JSONObject jsonObject, AdvertEffectReq advertEffectReq) {

        // 参数对象非空判断
        if (null == advertEffectReq) {
            jsonObject.put("record", ErrorCode.E0110001.geteCode());
            jsonObject.put("redesc", ErrorCode.E0110001.geteDesc());

            return Boolean.FALSE;
        }

        // tuiaid非空判断
        if (null == advertEffectReq.getA_tuiaId() || StringUtils.isBlank(advertEffectReq.getA_tuiaId())) {
            jsonObject.put("record", ErrorCode.E0110002.geteCode());
            jsonObject.put("redesc", ErrorCode.E0110002.geteDesc());

            return Boolean.FALSE;
        }

        // cid非空判断
        if (null == advertEffectReq.getA_cid()) {
            jsonObject.put("record", ErrorCode.E0110003.geteCode());
            jsonObject.put("redesc", ErrorCode.E0110003.geteDesc());

            return Boolean.FALSE;
        }

        //a_timeStam非空判断
        if (null == advertEffectReq.getA_timeStamp() || StringUtils.isBlank(advertEffectReq.getA_timeStamp())) {
            jsonObject.put("record", ErrorCode.E0110003.geteCode());
            jsonObject.put("redesc", ErrorCode.E0110003.geteDesc());

            return Boolean.FALSE;
        }

        // ip非空判断
        if (null == advertEffectReq.getIp() || StringUtils.isBlank(advertEffectReq.getIp())) {
            jsonObject.put("record", ErrorCode.E0110005.geteCode());
            jsonObject.put("redesc", ErrorCode.E0110005.geteDesc());

            return Boolean.FALSE;
        }

        // signature非空判断
        if (null == advertEffectReq.getSignature() || StringUtils.isBlank(advertEffectReq.getSignature())) {
            jsonObject.put("record", ErrorCode.E0110008.geteCode());
            jsonObject.put("redesc", ErrorCode.E0110008.geteDesc());

            return Boolean.FALSE;
        }

        return true;
    }


    /**
     * 校验签名是否正确
     *
     * @param jsonObject
     * @param advertEffectReq
     * @return
     */
    private Boolean checkSignature(JSONObject jsonObject, AdvertEffectReq advertEffectReq) {
        ArrayList<String> list = new ArrayList<>();
        list.add(advertEffectReq.getA_tuiaId());
        list.add(String.valueOf(advertEffectReq.getA_cid()));
        list.add(advertEffectReq.getA_timeStamp());
        list.add(Constants.TUIA_ADVERT_EFFECT_KEY);
        Collections.sort(list);

        // 4.获取token参数进行sha1加密
        String signStr = list.get(0) + list.get(1) + list.get(2) + list.get(3);

        try {
            String sigin = SHA1.sha1(signStr);

            if (!sigin.equals(advertEffectReq.getSignature())) {
                jsonObject.put("record", E0110006.geteCode());
                jsonObject.put("redesc", E0110006.geteDesc());
                return Boolean.FALSE;
            }
            return Boolean.TRUE;
        } catch (Exception e) {
            jsonObject.put("record", E0110006.geteCode());
            jsonObject.put("redesc", E0110006.geteDesc());
            return Boolean.FALSE;
        }
    }

    /**
     * 获取头条信息(直投页展示)
     *
     * @param request
     * @return
     * @throws ActivityException
     */
    @Override
    public String getTNewdata(HttpServletRequest request, String category, String refreshTime) throws ActivityException {
        //限制用户访问次数
        if(isVisitLimited(RequestLocal.get().getCid().toString())) {
            return "";
        }

        //原今日头条接口获取数据，去除原广告
        List<JSONObject> newsList = getTNewsList(request, category, refreshTime);
        Cat.logMetricForCount("活动中心-今日头条数据");
        return JSONObject.toJSONString(getDataList(request, newsList));
    }

    /**
     * 是否是访问受限制用户,1分钟15次,超过禁止访问2分钟
     *
     * @param cId
     * @return
     */
    private Boolean isVisitLimited(String cId) {
        try {
            Boolean isVisitLimited = visitLimitCache.get(cId);
            if(!isVisitLimited) {
                Calendar calendar = Calendar.getInstance();
                String key = CachedKeyUtils.getRedisKey(CacheKey.TOUTIAO_VISIT_COUNT, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE));
                Long time = stringRedisTemplate.opsForHash().increment(key, cId, 1);
                if(time == 1) {
                    stringRedisTemplate.expire(key, 1, TimeUnit.MINUTES);
                }
                if(time > 14) {
                    visitLimitCache.put(cId, true);
                    return true;
                }
                return false;
            }
            return true;
        } catch (ExecutionException e) {
            log.warn("getTNewdata.isVisitLimited have ExecutionException", e);
            return false;
        } catch (DataAccessException e) {
            log.warn("getTNewdata.isVisitLimited have DataAccessException", e);
            return false;
        }

    }

    /**
     * 获取今日头条新闻
     *
     * @param request
     * @param category
     * @param refreshTime
     * @return
     * @throws ActivityException
     */
    private List<JSONObject> getTNewsList(HttpServletRequest request, String category, String refreshTime) throws ActivityException {
        String oldData = getTdata(request, category, refreshTime);
        return getTNewsList(oldData);
    }

    /**
     * 获取今日头条广告
     *
     * @param request
     * @param adCount
     * @return
     */
    private List<JSONObject> getTAdsList(HttpServletRequest request, int adCount) {
        List<JSONObject> adList = new ArrayList<>();

        //构建今日头条广告请求参数
        TouTiaoBidRequest touTiaoBidRequest = new TouTiaoBidRequest();
        touTiaoBidRequest.setRequestId(UUID.randomUUID().toString().replace("-", ""));
        touTiaoBidRequest.setIp(RequestTool.getIpAddr(request));
        touTiaoBidRequest.setUid(RequestLocal.get().getCid().toString());
        touTiaoBidRequest.setApiVersion("1.7");
        touTiaoBidRequest.setSourceType("app");
        touTiaoBidRequest.setUa(RequestTool.getUA(RequestTool.getUserAgent(request)));

        Adslot adslot = new Adslot();
        adslot.setAdtype(Adslot.AD_TYPE_FLOW);
        adslot.setAdCount(adCount);
        adslot.setAcceptedSize(0, 0);
        if ("IOS".equals(touTiaoBidRequest.getUa())) {
            touTiaoBidRequest.getApp().setAppid(Constants.THIRDVARIABLE.TOUTIAO_IOS_APPID);
            adslot.setId(Constants.THIRDVARIABLE.TOUTIAO_FATION_IOS_SLOTID);
        } else {
            touTiaoBidRequest.getApp().setAppid(Constants.THIRDVARIABLE.TOUTIAO_ANDROID_APPID);
            adslot.setId(Constants.THIRDVARIABLE.TOUTIAO_FATION_ANDROID_SLOTID);
        }
        touTiaoBidRequest.getAdslots().add(adslot);

        //调用今日头条广告获取接口
        String adResponse = HttpRequestTool.requestByPost(Constants.CUSTOM.TOUTIAO_AD_INFO_HTTP_DOMAIN, JsonUtils.objectToString(touTiaoBidRequest));
        JSONObject adJson = JSONObject.parseObject(adResponse);
        if (adJson.getString("status_code").equals("20000")) {
            JSONArray array = adJson.getJSONArray("ads");
            if (CollectionUtils.isNotEmpty(array)) {
                array.forEach(o -> adList.add((JSONObject) o));
            }
        }
        return adList;
    }

    /**
     * 广告事件上报
     *
     * @param request
     * @param url
     * @param pageId     直投页id
     * @param action     曝光／点击
     * @return
     * @throws ActivityException
     */
    @Override
    public String getTNewAdReport(HttpServletRequest request, String url, Integer pageId, String action) throws ActivityException {
        Map<String, Object> params = new HashMap<>();
        params.put("consumer_id", RequestLocal.get().getCid());
        params.put("appid", RequestLocal.get().getAppId());
        params.put("action", action);
        params.put("url", url);
        params.put("delivery_type", DeliveryType.DIRECT_PAGE);
        params.put("pageId", pageId);
        InnerLogService.tadClickLog(params, new JSONObject());
        return "ok";
    }

    @Override
    public Map<String, String> rechargeCash(Map<String, String> map, BalanceType balanceType) {
        String appKey = map.get("appKey");
        Map<String, String> result = new HashMap<>();
        MediaAppDataDto appByAppKey = null;
        try {
            appByAppKey = appService.findAppByAppKey(appKey);
        } catch (Exception e) {
            result.put("code", RECHARGE_FAILED);
            result.put("msg", "获取媒体失败");
        }
        if (!checkExpire(map) || !checkSign(map,appByAppKey)) {
            // 这个3是随便写的...具体看文档
            result.put("code",RECHARGE_FAILED);
            result.put("msg","超时或者密钥错误");
            result.put("orderId",map.get("orderId"));
            return result;
        }

        Long cid = Long.parseLong(map.get("userId"));
        Long appId = appByAppKey.getAppId();
        try {
            BalanceRecordAdd add = new BalanceRecordAdd();
            add.setAmount(Long.parseLong(BalanceType.CASH.equals(balanceType)? MathUtil.yuan2Fen(map.get("score")) : map.get("score")));
            add.setUserId(cid);
            add.setBalanceType(balanceType);
            add.setRecordType(BalanceRecordType.ACTIVITY);
            add.setAppId(appId);
            add.setSlotId(0L);
            Map<String, String> ext = new HashMap<>();
            ext.put("prizeFlag", map.get("prizeFlag"));
            String extInfo = JSONObject.toJSONString(ext);
            add.setExtInfo(extInfo);
            Boolean aBoolean = remoteUserBalanceService.updateAmount(add);
            if (aBoolean) {
                result.put("code", RECHARGE_SUCCESS);
            } else {
                // 具体看文档
                result.put("code", RECHARGE_FAILED);
                result.put("msg", "更新余额异常");
            }
        } catch (Exception e) {
            // 活动center异常
            result.put("code", RECHARGE_FAILED);
            result.put("msg", "center充值失败");
        }
        return result;
    }

    /**
     * 验签
     *
     * @param map 封装参数的map
     * @param appByAppKey
     * @return 校验结果
     */
    private Boolean checkSign(Map<String, String> map, MediaAppDataDto appByAppKey)  {
        String appKey = map.get("appKey");
        String timestamp = map.get("timestamp");
        String prizeFlag = map.get("prizeFlag");
        String orderId = map.get("orderId");
        String appSecret = appByAppKey.getAppSecret();
        StringBuilder sb = new StringBuilder();
        sb.append(timestamp);// 时间戳
        sb.append(prizeFlag);// 虚拟商品标识//
        sb.append(orderId);// 订单号
        sb.append(appKey);// 媒体信息
        sb.append(appSecret);// 媒体密钥
        try {
            String sign = MD5.md5(sb.toString());
            if (map.get("sign").equals(sign)) {
                return true;
            }
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            log.warn("虚拟商品充值签名失败 msg={}", e.getMessage(), e);
        }
        return false;
    }

    private Boolean checkExpire(Map<String, String> map) {
        String timestamp = map.get("timestamp");
        Date date ;
        try {
            date = new Date(Long.valueOf(timestamp));
        } catch (Exception e) {
            return false;
        }
        // 5 分钟失效
        return date.after(new Date(new Date().getTime() - 5 * 60 * 1000L));
    }

    /**
     * 今日头条-新版广告请求（曝光+点击）
     *
     * @param url
     * @param params
     */
    private void invokeTouTiaoNew(String url, Map<String, Object> params) {
        HttpGet httpGet = new HttpGet(url);
        FutureCallback<HttpResponse> handler = new FutureCallback<HttpResponse>() {
            @Override
            public void completed(HttpResponse result) {
                try {
                    int statusCode = result.getStatusLine().getStatusCode();
                    HttpEntity entity = result.getEntity();
                    String reponse = "success";
                    JSONObject jsonObject = null;
                    if (statusCode != HttpStatus.SC_OK) {
                        reponse = "failReason:" + EntityUtils.toString(entity, "utf-8");
                    } else {
                        jsonObject = JSON.parseObject(EntityUtils.toString(entity, "utf-8"));
                    }
                    params.put("response", reponse);
                    InnerLogService.tadClickLog(params, jsonObject == null ? new JSONObject() : jsonObject);
                } catch (Exception e) {
                    log.error("[TouTiao-NewAdvert] invokeTouTiao throw exception, details: ", e);
                } finally {
                    if (result != null) {
                        try {
                            EntityUtils.consume(result.getEntity());
                        } catch (IOException e) {
                            log.warn("[HttpEntity] consume failed, detial: ", e);
                        }
                    }
                }
            }

            @Override
            public void failed(Exception ex) {
                log.error("[TouTiao-NewAdvert] invokeTouTiaoNew throw exception, details: ", ex);
            }

            @Override
            public void cancelled() {
                log.error("[TouTiao-NewAdvert] invokeTouTiaoNew has been cancelled.");
            }
        };
        httpAsyncClientPool.submit(httpGet, handler);
    }

    @Override
    public TouTiaoData getTNewdata2(HttpServletRequest request, String category, String refreshTime) throws ActivityException {
        //限制用户访问次数
        if (isVisitLimited(RequestLocal.get().getCid().toString())) {
            return null;
        }

        //信息流接口获取新闻
        TouTiaoData newsData = getTouTiaoData(request, category, refreshTime);
        if(newsData != null) {
            List<JSONObject> newsList = getTNewsList(newsData.getData());
            //获取新闻+广告
            newsData.setData(JSONObject.toJSONString(getDataList(request, newsList)));
        }
        Cat.logMetricForCount("活动中心-今日头条数据");
        return newsData;
    }

    /**
     * 获取今日头条新闻
     *
     * @param newsData
     * @return
     * @throws ActivityException
     */
    private List<JSONObject> getTNewsList(String newsData) throws ActivityException {
        List<JSONObject> newsList = new ArrayList<>();

        try {
            if (StringUtils.isNotBlank(newsData)) {
                List<JSONObject> oldDataList = (List<JSONObject>) JSONObject.parse(newsData);
                for (JSONObject object : oldDataList) {
                    if (!object.containsKey("ad_id")) {
                        newsList.add(object);
                    }
                }
            }
        } catch (Exception e) {
            logger.info("今日头条getTNewsList获取数据异常", e);
        }
        return newsList;
    }

    /**
     * 获取头条完整数据
     * @return
     */
    private List<JSONObject> getDataList(HttpServletRequest request, List<JSONObject> newsList) {
        List<JSONObject> dataList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(newsList)) {
            //广告数量
            int adCount = newsList.size() / 4;
            //获取广告
            List<JSONObject> adList = getTAdsList(request, adCount);

            //组装新闻和广告
            if (CollectionUtils.isNotEmpty(adList)) {
                int position = 1;//当前位置
                int initPosition = 2;//第2条为广告
                int step = 4;//每4条新闻一条广告
                for (JSONObject news : newsList) {
                    if (adList.size() > position / step && (position == initPosition || position % step == initPosition)) {
                        dataList.add(adList.get(position / step));
                        position++;
                    }
                    dataList.add(news);
                    position++;
                }
            } else {
                dataList = newsList;
            }
        }
        return dataList;
    }
}
