/**
 * Project Name:tuia-youtui-web<br>
 * File Name:GuidePageServiceImpl.java<br>
 * Package Name:com.duiba.tuia.youtui.web.service.impl<br>
 * Date:2017年3月7日下午7:26:35<br>
 * Copyright (c) 2017, duiba.com.cn All Rights Reserved.<br>
 */

package com.duiba.tuia.youtui.web.service.impl;

import cn.com.duiba.tuia.activity.center.api.constant.BalanceType;
import cn.com.duiba.tuia.activity.center.api.constant.IsReward;
import cn.com.duiba.tuia.activity.center.api.constant.PageType;
import cn.com.duiba.tuia.activity.center.api.dto.*;
import cn.com.duiba.tuia.activity.center.api.remoteservice.RemoteGuidePageService;
import cn.com.duiba.tuia.activity.center.api.util.CollectionTool;
import cn.com.duiba.tuia.ssp.center.api.dto.ActivityDto;
import cn.com.duiba.tuia.ssp.center.api.remote.RemoteActivityCenterService;
import cn.com.duiba.tuia.ssp.center.api.remote.RemoteActivityTagService;
import cn.com.duiba.wolf.dubbo.DubboResult;
import cn.com.tuia.advert.model.AdvertDirectDto;
import cn.com.tuia.advert.model.DirectObtainAdvertReq;
import cn.com.tuia.advert.service.IAdvertDirectService;
import com.duiba.tuia.youtui.web.constant.CacheKey;
import com.duiba.tuia.youtui.web.constant.ErrorCode;
import com.duiba.tuia.youtui.web.constant.TempFunction;
import com.duiba.tuia.youtui.web.exception.ActivityException;
import com.duiba.tuia.youtui.web.service.ActivityService;
import com.duiba.tuia.youtui.web.service.BaseCacheService;
import com.duiba.tuia.youtui.web.service.GuidePageService;
import com.duiba.tuia.youtui.web.tool.CachedKeyUtils;
import com.duiba.tuia.youtui.web.tool.RequestLocal;
import com.duiba.tuia.youtui.web.tool.RequestTool;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * ClassName: GuidePageServiceImpl <br/>
 * date: 2017年3月7日 下午7:26:35 <br/>
 *
 * @author youhaijun
 * @since JDK 1.6
 */
@Service
public class GuidePageServiceImpl extends BaseCacheService implements GuidePageService, InitializingBean {

    @Autowired
    private RemoteGuidePageService remoteGuidePageService;

    @Autowired
    private RemoteActivityCenterService remoteActivityCenterService;

    @Autowired
    private RemoteActivityTagService remoteActivityTagService;

    @Autowired
    private IAdvertDirectService iAdvertDirectService;

    @Autowired
    private ActivityService activityService;

    private LoadingCache<Long, List<ActivityDto>> directGuideCache;

    private LoadingCache<List<Long>, List<ActivityDto>> directNotGuideCache;

    private LoadingCache<List<Long>, List<GuidePageDto>> guidesCache;

    private LoadingCache<List<Long>, List<Long>> mainMeetActivityIds;

    @Override
    public void afterPropertiesSet() {

        directGuideCache = CacheBuilder
                .newBuilder()
                .maximumSize(2000)
                .refreshAfterWrite(1, TimeUnit.MINUTES)//缓存1分钟刷新一次(load不到新数据时仍返回旧数据)
                .expireAfterWrite(5, TimeUnit.MINUTES)//缓存5分钟强制刷新一次
                .build(new CacheLoader<Long, List<ActivityDto>>() {

                    @Override
                    public List<ActivityDto> load(Long key) {
                        return getAppGuide(key);
                    }
                });

        directNotGuideCache = CacheBuilder
                .newBuilder()
                .maximumSize(2000)
                .refreshAfterWrite(1, TimeUnit.MINUTES)//缓存1分钟刷新一次(load不到新数据时仍返回旧数据)
                .expireAfterWrite(5, TimeUnit.MINUTES)//缓存5分钟强制刷新一次
                .build(new CacheLoader<List<Long>, List<ActivityDto>>() {

                    @Override
                    public List<ActivityDto> load(List<Long> key) {
                        return getNotGuideByPageIds(key);
                    }
                });

        guidesCache = CacheBuilder
                .newBuilder()
                .maximumSize(500)
                .refreshAfterWrite(30, TimeUnit.SECONDS)
                .build(new CacheLoader<List<Long>, List<GuidePageDto>>() {
                    @Override
                    public List<GuidePageDto> load(List<Long> pageIds) {
                        return getGuidePages(pageIds);
                    }
                });

        mainMeetActivityIds = CacheBuilder
                .newBuilder()
                .maximumSize(50)
                .refreshAfterWrite(1, TimeUnit.MINUTES)
                .build(new CacheLoader<List<Long>, List<Long>>() {
                    @Override
                    public List<Long> load(List<Long> ids) {
                        return invokeGetActivityIdByMainMeetIds(ids);
                    }
                });
    }

    @Override
    public GuidePageDto getGuidePage(Long id) throws ActivityException {

        DubboResult<GuidePageDto> result = remoteGuidePageService.getGuidePageDetail(id);
        if (!result.isSuccess() || result.getResult().getIsDeleted()) {
            logger.warn("[Tuia-Activity]remoteGuidePageService.getGuidePageDetail happen error, the pageId=[{}], and because of=[{}]",
                    id, result.getMsg());
            throw new ActivityException(ErrorCode.E0400010);
        }
        return result.getResult();
    }

    @Override
    public GuidePageSkinDto getGuidePageSkin(Long id) throws ActivityException {

        String key = CachedKeyUtils.getRedisKey(CacheKey.GUIDE_SKIN_KEY, id);
        Object object = advancedCacheClient.get(key);
        if (null != object) {
            return (GuidePageSkinDto) object;
        }

        DubboResult<GuidePageSkinDto> result = remoteGuidePageService.getGuidePageSkinDetail(id);
        if (!result.isSuccess() || null == result.getResult()) {
            logger.warn("[Tuia-Activity]remoteGuidePageService.getGuidePageSkinDetail happen error, the skinId=[{}], and because of=[{}]",
                    id, result.getMsg());
            throw new ActivityException(ErrorCode.E0400011);
        }
        advancedCacheClient.set(key, result.getResult(), ONE_DAY, TimeUnit.SECONDS);
        return result.getResult();
    }


    public Long getActCenterId() throws ActivityException {
        DubboResult<Long> result = remoteGuidePageService.getRecentGuidePage(PageType.ACTCENTER.getCode());
        if (!result.isSuccess()) {
            logger.warn("[Tuia-Activity]remoteGuidePageService.getRecentGuidePage happen error, because of=[{}]",
                    result.getMsg());
        }
        return result.getResult();
    }

    /**
     * 查询该app下 定向的所有引导页的 推广计划信息
     *
     * @param appId 媒体id
     * @return list ActivityDto
     */
    private List<ActivityDto> getAppGuide(Long appId) {
        DubboResult<List<ActivityDto>> dr = this.remoteActivityCenterService.getDirectActByAppId(appId, 2);
        DubboResult<List<ActivityDto>> dr2 = this.remoteActivityCenterService.getDirectActByAppId(appId, 21);
        if (!dr.isSuccess()) {
            logger.warn("remoteTuiaActivityService.getAppActByAppId happen error,the msg=" + dr.getMsg());
        }
        List<ActivityDto> result = Lists.newArrayList();
        if (dr.getResult() != null) {
            result.addAll(dr.getResult());
        }
        if (dr2.getResult() != null) {
            result.addAll(dr2.getResult());
        }
        return result;
    }

    /**
     * 查询一批引导页id的推广计划
     * 返回可用的，非定向的推广计划。
     *
     * @param pageIds 引导页id
     * @return
     */
    private List<ActivityDto> getNotGuideByPageIds(List<Long> pageIds) {
        DubboResult<List<ActivityDto>> dr = this.remoteActivityCenterService.getAvailablePlanList(pageIds, 2);
        DubboResult<List<ActivityDto>> dr2 = this.remoteActivityCenterService.getAvailablePlanList(pageIds, 21);
        if (!dr.isSuccess()) {
            logger.warn("remoteActivityCenterService.getAvailablePlanList happen error,the msg=" + dr.getMsg());
        }
        List<ActivityDto> dtos = dr.getResult();
        List<ActivityDto> rs = Lists.newArrayList();
        if (CollectionUtils.isNotEmpty(dtos)) {
            dtos.forEach(dto -> {
                if (Integer.valueOf(0).equals(dto.getIsDirectMedia())) {
                    rs.add(dto);
                }
            });

        }
        List<ActivityDto> dtos2 = dr2.getResult();
        if (CollectionUtils.isNotEmpty(dtos2)) {
            dtos2.forEach(dto2 -> {
                if (Integer.valueOf(0).equals(dto2.getIsDirectMedia())) {
                    rs.add(dto2);
                }
            });

        }
        return rs;
    }

    private boolean checkActivityDto(ActivityDto dto) {
        return dto != null && dto.getIsDelete() != null && dto.getIsDelete() == 0 && dto.getIsEnable() != null
                && dto.getIsEnable() == 1 && dto.getIsPublish() != null && dto.getIsPublish() == 1;
    }

    /**
     * 查询定向到某媒体的可用推广计划list
     *
     * @param appId    媒体id
     * @param pageType 页面类型
     * @return 推广计划list
     */
    private List<ActivityDto> getAppGuideByPageType(Long appId, PageType pageType) throws ActivityException {
        try {
            List<ActivityDto> list = getAppGuideByCache(appId);
            if (CollectionUtils.isEmpty(list)) {
                return Collections.emptyList();
            }
            List<ActivityDto> rs = Lists.newArrayList();
            //过滤引导页类型
            list.forEach(dto -> {
                if (pageType.getCode().equals(dto.getActivityType()) && checkActivityDto(dto)) {
                    rs.add(dto);
                }
            });

            return rs;
        } catch (Exception e) {
            throw new ActivityException();
        }
    }

    private List<ActivityDto> getAppGuideByCache(Long appId) throws ActivityException {
        try {
            return directGuideCache.get(appId);
        } catch (Exception e) {
            throw new ActivityException();
        }
    }

    private List<ActivityDto> getNotGuideByCache(List<Long> pageIds) throws ActivityException {
        try {
            return directNotGuideCache.get(pageIds);
        } catch (Exception e) {
            throw new ActivityException();
        }
    }

    @Override
    public Long getDirectPageId(Long appId, ProbDto probDto) throws ActivityException {
        List<RateDto> directs = probDto.getDirectPageList();
        if (CollectionUtils.isEmpty(directs)) {
            return null;
        }
        // directId,RateDto
        Map<Long, RateDto> directMap = Maps.newHashMap();
        directs.forEach(dto -> {
            if (dto.getRate() != null && dto.getRate() > 0) {
                directMap.put(dto.getId(), dto);
            }
        });

        if (directMap.isEmpty()) {
            return null;
        }

        Set<Long> rs = Sets.newHashSet();
        try {
            //找出在 定向的且可投放的推广计划中的 直投页id
            List<ActivityDto> guides = this.getAppGuideByPageType(appId, PageType.DIRECT);
            if (CollectionUtils.isNotEmpty(guides)) {
                guides.forEach(dto -> {
                    if (directMap.containsKey(dto.getActivityId())) {
                        rs.add(dto.getActivityId());
                    }
                });
            }
            //找出在非定向的可投放的推广计划中的 直投页id
            List<ActivityDto> notGuides = this.getNotGuideByCache(Lists.newArrayList(directMap.keySet()));
            if (CollectionUtils.isNotEmpty(notGuides)) {
                notGuides.forEach(dto -> {
                    if (directMap.containsKey(dto.getActivityId())) {
                        rs.add(dto.getActivityId());
                    }
                });
            }
        } catch (Exception e) {
            throw new ActivityException();
        }

        List<RateDto> rates = Lists.newArrayList();
        rs.forEach(pageId -> rates.add(directMap.get(pageId)));

        RateDto rate = ProbDto.calRate(rates, null);
        return rate == null ? null : rate.getId();
    }

    @Override
    public Long getGuidePageId(Long appId, ProbDto probDto, PageType pageType) throws ActivityException {
        // 1 声明
        // 1.1 配置的引导页(主会场,直投页)
        List<RateDto> guides;
        // 1.2 可出的引导页
        List<RateDto> suitable;
        // 1.3 概率计算得出的最终结果
        RateDto result;

        // 2 优先出定向逻辑
        // 2.1 获取配置的主会场/直投页 id列表(管理端入口配置，浮标/返回拦截)
        if (PageType.MAINMEET.equals(pageType)) {
            guides = probDto.getMainMeetList();
        } else if (PageType.DIRECT.equals(pageType)) {
            guides = probDto.getDirectPageList();
        } else {
            //该方法只支持主会场和直投页
            return null;
        }

        if (CollectionUtils.isEmpty(guides)) {
            return null;
        }
        // 2.2 配置的id列表转成map形式，便于处理后面的逻辑
        Map<Long, RateDto> guideMap = guides.stream().filter(rate -> (rate.getRate() != null && rate.getRate() > 0)).collect(Collectors.toMap(RateDto::getId, Function.identity()));

        // 2.3 获取该媒体定向的可用的推广计划
        List<ActivityDto> plans = this.getAppGuideByPageType(appId, pageType);

        // 2.4 拿出定向该app的推广计划与配置的引导页的交集
        suitable = CollectionTool.getContainsList(guideMap, plans, ActivityDto::getActivityId);

        // 2.5 概率计算结果
        result = ProbDto.calRate(suitable, null);

        // 2.6 有结果直接返回
        if (result != null) {
            return result.getId();
        }

        // 3.非定向的逻辑
        // 3.1 获取配置的id关联的可用推广计划
        plans = this.getNotGuideByCache(Lists.newArrayList(guideMap.keySet()));
        suitable = CollectionTool.getContainsList(guideMap, plans, ActivityDto::getActivityId);
        result = ProbDto.calRate(suitable, null);

        // 临时需求
        result = checkTmpDirectId(suitable, result);

        if (result != null) {
            return result.getId();
        }

        return null;

    }

    /**
     * 临时直投页广告是否有效，无效降级
     *
     * @param suitable
     * @param result
     * @return
     */
    private RateDto checkTmpDirectId(List<RateDto> suitable, RateDto result) {
        if (result != null) {
            // 返回拦截如果包含传奇直投广告 967，其上广告ID<27967>不可用，不出该直投页
            if (result.getId().equals(TempFunction.CHUAN_QI_DIRECT_PAGE_ID)) {
                DirectObtainAdvertReq advertReq = new DirectObtainAdvertReq();
                advertReq.setAdvertIds(Lists.newArrayList(TempFunction.CHUAN_QI_DIRECT_ADVERT_ID));
                advertReq.setConsumerId(RequestLocal.get().getCid());
                advertReq.setAppId(RequestLocal.get().getAppId());
                advertReq.setDeviceId(RequestLocal.get().getDeviceId());
                advertReq.setUa(RequestTool.getUA(RequestTool.getUserAgent(RequestLocal.get().getRequest())));
                advertReq.setIp(RequestLocal.get().getIp());
                if (!StringUtils.isEmpty(RequestLocal.get().getSlotId())) {
                    advertReq.setSlotId(Long.valueOf(RequestLocal.get().getSlotId()));
                }
                advertReq.setUserAgent(RequestTool.getUserAgent(RequestLocal.get().getRequest()));
                advertReq.setTimestamp(System.currentTimeMillis());
                advertReq.setOs(RequestLocal.get().getOs());
                DubboResult<List<AdvertDirectDto>> dr = this.iAdvertDirectService.getValidAdverts(advertReq);
                if (!dr.isSuccess() || CollectionUtils.isEmpty(dr.getResult())) {
                    //异常或者广告无效 移除967直投页
                    result = ProbDto.calRate(suitable.stream().filter(e -> !e.getId().equals(TempFunction.CHUAN_QI_DIRECT_PAGE_ID)).collect(Collectors.toList()), null);
                }
            }
        }
        return result;
    }


    @Override
    public List<GuidePageBlockDto> getDirectBlocks(Long pageId) {
        DubboResult<List<GuidePageBlockDto>> dr = this.remoteGuidePageService.selectDirectBlock(pageId);
        if (!Boolean.TRUE.equals(dr.isSuccess())) {
            logger.warn("[Tuia-Activity]remoteGuidePageService.getDirectBlocks happen error, because of=[{}]",
                    dr.getMsg());
        }
        GuidePageDto guidePageDetail = remoteGuidePageService.getGuidePageDetail(pageId).getResult();
        GuidePageDto.ExtInfo extInfo = guidePageDetail.getExtInfo();
        if (extInfo != null && BalanceType.CASH.getType() == extInfo.getDirectType() && extInfo.getIsReward() == IsReward.ON.getType()) {
            GuidePageMoneyConfigDto moneyConfigDto = guidePageDetail.getGuidePageMoneyConfigDto();
            calMoneyConfigRate(dr.getResult(), moneyConfigDto);
        }
        return dr.getResult();
    }

    private void calMoneyConfigRate(List<GuidePageBlockDto> list, GuidePageMoneyConfigDto moneyConfigDto) {
        List<GuidePageMoneyConfigDto.Regions> regions = moneyConfigDto.getRegions();
        switch (moneyConfigDto.getRewardRegion()) {
            case 1: {//固定区块
                fixedBlock(list, regions);
                break;
            }
            case 2: {//区域内随机
                regionRandom(list, regions);
                break;
            }
            case 3: {//全局随机
                if (!CollectionUtils.isEmpty(list)) {
                    list.get(ThreadLocalRandom.current().nextInt(list.size() - 1)).setIsReward(true);
                }
                break;
            }
            default:
                break;
        }
    }

    private void regionRandom(List<GuidePageBlockDto> list, List<GuidePageMoneyConfigDto.Regions> regions) {
        regions.forEach(r -> {
            List<GuidePageBlockDto> tempList = new ArrayList<>();
            list.forEach(e -> {
                if (!StringUtils.isEmpty(e.getRegionName()) && e.getRegionName().equals(r.getRegionName())) {
                    tempList.add(e);
                }
            });
            if (!CollectionUtils.isEmpty(tempList)) {
                tempList.get(ThreadLocalRandom.current().nextInt(tempList.size() - 1)).setIsReward(true);
            }
        });
    }

    private void fixedBlock(List<GuidePageBlockDto> list, List<GuidePageMoneyConfigDto.Regions> regions) {
        for (GuidePageMoneyConfigDto.Regions region : regions) {
            list.forEach(e -> {
                if (!StringUtils.isEmpty(e.getBlockName()) && e.getBlockName().equals(region.getBlockName())) {
                    e.setIsReward(true);
                }
            });
        }
    }

    @Override
    public List<GuidePageDto> getGuidePages(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        DubboResult<List<GuidePageDto>> dr = this.remoteGuidePageService.getGuidePageList(ids);
        if (!Boolean.TRUE.equals(dr.isSuccess())) {
            logger.warn("[Tuia-Activity]remoteGuidePageService.getGuidePageList happen error, because of=[{}]",
                    dr.getMsg());
        }
        return dr.getResult();
    }

    @Override
    public List<GuidePageDto> getGuidePagesFromCache(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        try {
            return this.guidesCache.get(ids);
        } catch (ExecutionException e) {
            logger.warn("guidesCache get error", e);
            return Collections.emptyList();
        }
    }

    @Override
    public GuidePageMoneyConfigDto getGuidePageMoneyConfigDto(Long pageId) {
        DubboResult<GuidePageMoneyConfigDto> dr = remoteGuidePageService.getGuidePageMoneyConfigDto(pageId);
        if (!Boolean.TRUE.equals(dr.isSuccess())) {
            logger.warn("[Tuia-Activity]remoteGuidePageService.getGuidePageMoneyConfigDto happen error, because of=[{}]",
                    dr.getMsg());
        }
        return dr.getResult();
    }

    @Override
    public Map<Long, Set<Long>> getMainMeetActivityTag(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return Collections.emptyMap();
        }
        List<Long> activityIds = this.getActivityIdByMainMeetIds(ids);

        return activityService.getActivitysTags(activityIds);
    }

    @Override
    public List<Long> getActivityIdByMainMeetIds(List<Long> ids) {
        try {
            if (CollectionUtils.isEmpty(ids)) {
                return Collections.emptyList();
            }
            return this.mainMeetActivityIds.get(ids);
        } catch (Exception e) {
            logger.warn("mainMeetActivityIds.get error", e);
            return Collections.emptyList();
        }
    }

    private List<Long> invokeGetActivityIdByMainMeetIds(List<Long> ids) {
        Set<Long> activityIds = Sets.newHashSet(this.remoteGuidePageService.selectActivityIdsByMainMeetIds(ids));
        return Lists.newArrayList(activityIds);
    }

}
