package com.tzld.piaoquan.ad.engine.service.feature;

import com.google.common.collect.ImmutableMap;
import com.google.common.reflect.TypeToken;
import com.tzld.piaoquan.ad.engine.commons.score.ScoreParam;
import com.tzld.piaoquan.ad.engine.commons.util.CommonUtils;
import com.tzld.piaoquan.ad.engine.commons.util.JSONUtils;
import com.tzld.piaoquan.ad.engine.service.predict.v2.PredictContext;
import com.tzld.piaoquan.ad.engine.service.remote.FeatureV2RemoteService;
import com.tzld.piaoquan.recommend.feature.domain.ad.base.AdRequestContext;
import com.tzld.piaoquan.recommend.feature.model.feature.FeatureKeyProto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.*;

@Slf4j
@Component
public class FeatureService {

    @Autowired
    private FeatureV2RemoteService remoteService;

    private static final String cidUkFormat = "c:%s:%s";
    private static final String cidUkDoubleFieldFormat = "c:%s:%s:%s";
    private static final String avUkFormat = "av:%s:%s";
    private static final String vidUkFormat = "v:%s:%s";
    private static final String midUkFormat = "m:%s:%s";
    private static final String skuUkFormat = "s:%s:%s";
    private static final String otherFormat = "o:%s:%s";

    public Feature getFeature(Collection<String> cidList, Collection<String> adVerIdList, List<Long> skuIdList, ScoreParam param) {
        AdRequestContext context = param.getRequestContext();
        List<FeatureKeyProto> protos = new ArrayList<>();
        for (String cidStr : cidList) {

            // cid
            protos.add(genWithCid("alg_cid_feature_basic_info", cidStr));
            protos.add(genWithCid("alg_cid_feature_cid_action", cidStr));

            // cid + region
            protos.add(genWithCidAndRegion("alg_cid_feature_region_action", cidStr, context.getRegion()));

            // cid + apptype
            protos.add(genWithCidAndAppType("alg_cid_feature_app_action", cidStr, context.getApptype()));

            // cid + week
            protos.add(genWithCidAndWeek("alg_cid_feature_week_action", cidStr, context.getWeek()));

            // cid + hour
            protos.add(genWithCidAndHour("alg_cid_feature_hour_action", cidStr, context.getHour()));

            // cid + brand
            if (StringUtils.isNotEmpty(context.getMachineinfoBrand())) {
                protos.add(genWithCidAndBrand("alg_cid_feature_brand_action", cidStr, context.getMachineinfoBrand()));
            }

            // cid + wechatversion
            protos.add(genWithCidAndWechatVersion("alg_cid_feature_weChatVersion_action", cidStr, context.getMachineinfoWechatversion()));

            // cid + vid
            if (Objects.nonNull(param.getVideoId())) {
                protos.add(genWithCidAndVid("alg_cid_feature_vid_cf", cidStr, param.getVideoId().toString()));
            }
        }
        // skuid
        for (Long skuId : skuIdList) {
            if (StringUtils.isNotEmpty(param.getMid())) {
                protos.add(genWithMidAndSkuId("alg_mid_feature_sku_action", param.getMid(), String.valueOf(skuId)));
            }
        }

        for (String adVerId : adVerIdList) {
            // adverid
            protos.add(genWithAdVerId("alg_cid_feature_adver_action", adVerId));
            if (StringUtils.isNotEmpty(param.getMid())) {
                protos.add(genWithMidAndAdVerId("alg_mid_feature_adver_action", param.getMid(), adVerId));
            }
        }

        // vid
        if (Objects.nonNull(param.getVideoId())) {
            protos.add(genWithVid("alg_cid_feature_vid_cf_rank", param.getVideoId().toString()));
            protos.add(genWithVid("alg_vid_feature_basic_info", param.getVideoId().toString()));
        }

        // mid
        if (StringUtils.isNotEmpty(param.getMid())) {
            protos.add(genWithMid("alg_mid_feature_ad_action", param.getMid()));
            protos.add(genWithMid("alg_mid_feature_return_tags", param.getMid()));
            protos.add(genWithMid("alg_mid_feature_share_tags", param.getMid()));
            protos.add(genWithMid("mid_return_video_cate", param.getMid()));
            protos.add(genWithMid("mid_share_video_cate", param.getMid()));
        }
        return this.invokeFeatureService(protos);
    }

    public Feature getFeatureV2(Collection<String> cidList, Collection<String> adVerIdList, List<Long> skuIdList, ScoreParam param,
                                String userLayer, Collection<String> custList, Collection<String> profList, Collection<String> cateList, Collection<String> landingList,
                                List<Pair<String, String>> landingAdvList, List<Pair<String, String>> landingCustList,
                                List<Pair<String, String>> landingProfList, List<Pair<String, String>> landingCateList) {
        AdRequestContext context = param.getRequestContext();
        List<FeatureKeyProto> protos = new ArrayList<>();
        for (String cidStr : cidList) {
            // cid
            protos.add(genWithCid("alg_cid_feature_basic_info", cidStr));
            protos.add(genWithCid("alg_cid_feature_cid_action", cidStr));

            // cid + region
            protos.add(genWithCidAndRegion("alg_cid_feature_region_action", cidStr, context.getRegion()));

            // cid + apptype
            protos.add(genWithCidAndAppType("alg_cid_feature_app_action", cidStr, context.getApptype()));

            // cid + week
            protos.add(genWithCidAndWeek("alg_cid_feature_week_action", cidStr, context.getWeek()));

            // cid + hour
            protos.add(genWithCidAndHour("alg_cid_feature_hour_action", cidStr, context.getHour()));

            // cid + brand
            if (StringUtils.isNotEmpty(context.getMachineinfoBrand())) {
                protos.add(genWithCidAndBrand("alg_cid_feature_brand_action", cidStr, context.getMachineinfoBrand()));
            }

            // cid + wechatversion
            protos.add(genWithCidAndWechatVersion("alg_cid_feature_weChatVersion_action", cidStr, context.getMachineinfoWechatversion()));

            // cid + vid
            if (Objects.nonNull(param.getVideoId())) {
                protos.add(genWithCidAndVid("alg_cid_feature_vid_cf", cidStr, param.getVideoId().toString()));
            }
        }
        // skuid
        for (Long skuId : skuIdList) {
            if (StringUtils.isNotEmpty(param.getMid())) {
                protos.add(genWithMidAndSkuId("alg_mid_feature_sku_action", param.getMid(), String.valueOf(skuId)));
            }
        }

        for (String adVerId : adVerIdList) {
            // adverid
            protos.add(genWithAdVerId("alg_cid_feature_adver_action", adVerId));
            if (StringUtils.isNotEmpty(param.getMid())) {
                protos.add(genWithMidAndAdVerId("alg_mid_feature_adver_action", param.getMid(), adVerId));
            }
        }

        // vid
        if (Objects.nonNull(param.getVideoId())) {
            protos.add(genWithVid("alg_cid_feature_vid_cf_rank", param.getVideoId().toString()));
            protos.add(genWithVid("alg_vid_feature_basic_info", param.getVideoId().toString()));
        }

        // mid
        if (StringUtils.isNotEmpty(param.getMid())) {
            protos.add(genWithMid("alg_mid_feature_ad_action", param.getMid()));
            protos.add(genWithMid("alg_mid_feature_return_tags", param.getMid()));
            protos.add(genWithMid("alg_mid_feature_share_tags", param.getMid()));
            protos.add(genWithMid("mid_return_video_cate", param.getMid()));
            protos.add(genWithMid("mid_share_video_cate", param.getMid()));
        }

        // expand feature
        protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_level_action", CommonUtils.getFeatureUniqueKey("j1", userLayer), ImmutableMap.of("level", userLayer)));
        for (String adv : adVerIdList) {
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j2", userLayer, adv);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_level_advertiser_action", uniqueKey, ImmutableMap.of("level", userLayer, "advertiser_id", adv)));
        }
        for (String cust : custList) {
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j3", userLayer, cust);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_level_customer_action", uniqueKey, ImmutableMap.of("level", userLayer, "customer_id", cust)));
        }
        for (String prof : profList) {
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j4", userLayer, prof);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_level_profession_action", uniqueKey, ImmutableMap.of("level", userLayer, "profession", prof)));
        }
        for (String cate : cateList) {
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j5", userLayer, cate);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_level_category_action", uniqueKey, ImmutableMap.of("level", userLayer, "category_name", cate)));
        }
        for (String cid : cidList) {
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j6", userLayer, cid);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_level_cid_action", uniqueKey, ImmutableMap.of("level", userLayer, "cid", cid)));
        }
        for (String landing : landingList) {
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j7", landing);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_landingpage_action", uniqueKey, ImmutableMap.of("landing_page_type", landing)));
        }
        for (Pair<String, String> pair : landingAdvList) {
            String landing = pair.getKey();
            String adv = pair.getValue();
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j8", landing, adv);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_landingpage_advertiser_action", uniqueKey, ImmutableMap.of("landing_page_type", landing, "advertiser_id", adv)));
        }
        for (Pair<String, String> pair : landingCustList) {
            String landing = pair.getKey();
            String cust = pair.getValue();
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j9", landing, cust);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_landingpage_customer_action", uniqueKey, ImmutableMap.of("landing_page_type", landing, "customer_id", cust)));
        }
        for (Pair<String, String> pair : landingProfList) {
            String landing = pair.getKey();
            String prof = pair.getValue();
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j10", landing, prof);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_landingpage_profession_action", uniqueKey, ImmutableMap.of("landing_page_type", landing, "profession", prof)));
        }
        for (Pair<String, String> pair : landingCateList) {
            String landing = pair.getKey();
            String cate = pair.getValue();
            String uniqueKey = CommonUtils.getFeatureUniqueKey("j11", landing, cate);
            protos.add(genWithKeyMap(otherFormat, "alg_cid_feature_landingpage_category_action", uniqueKey, ImmutableMap.of("landing_page_type", landing, "category_name", cate)));
        }
        return this.invokeFeatureService(protos);
    }

    private FeatureKeyProto genWithKeyMap(String format, String table, String uniqKey, Map<String, String> map) {
        FeatureKeyProto.Builder builder = FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(format, table, uniqKey))
                .setTableName(table);
        for (Map.Entry<String, String> entry : map.entrySet()) {
            builder.putFieldValue(entry.getKey(), entry.getValue());
        }
        return builder.build();
    }

    public Feature getPredictFeature(PredictContext context) {
        List<FeatureKeyProto> protos = new ArrayList<>();
        protos.add(genWithMidAndAppType("alg_ad_crowd_choose_feature_v2", context.getMid(), context.getAppType()));
        return this.invokeFeatureService(protos);
    }

    /**
     * 获取用户行为特征(用户分层、启动数、ror)
     *
     * @param mid
     * @return
     */
    public Feature getMidBehaviorFeature(String tableName, String mid) {
        List<FeatureKeyProto> protos = new ArrayList<>();
        protos.add(genWithMid(tableName, mid));
        return this.invokeFeatureService(protos);
    }


    public Feature invokeFeatureService(List<FeatureKeyProto> protos) {

        Map<String, String> featureMap = remoteService.getFeature(protos);
        if (featureMap == null) {
            log.warn("invokeFeatureService: featureMap is null, return empty feature");
            return new Feature();
        }

        Feature feature = new Feature();

        for (Map.Entry<String, String> entry : featureMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            String[] split = key.split(":");
            String prefix = split[0];
            String tableName = split[1];
            String id = split[2];
            switch (prefix) {
                case "c":
                    Map<String, Map<String, String>> tableFeatureMap = feature.getCidFeature().getOrDefault(id, new HashMap<>());
                    tableFeatureMap.put(tableName, JSONUtils.fromJson(value, new TypeToken<Map<String, String>>() {
                    }, Collections.emptyMap()));
                    feature.getCidFeature().put(id, tableFeatureMap);
                    break;
                case "av":
                    Map<String, Map<String, String>> avFeatureMap = feature.getAdVerFeature().getOrDefault(id, new HashMap<>());
                    avFeatureMap.put(tableName, JSONUtils.fromJson(value, new TypeToken<Map<String, String>>() {
                    }, Collections.emptyMap()));
                    feature.getAdVerFeature().put(id, avFeatureMap);
                    break;
                case "m":
                    feature.getUserFeature().put(tableName, JSONUtils.fromJson(value, new TypeToken<Map<String, String>>() {
                    }, Collections.emptyMap()));
                    break;
                case "v":
                    feature.getVideoFeature().put(tableName, JSONUtils.fromJson(value, new TypeToken<Map<String, String>>() {
                    }, Collections.emptyMap()));
                    break;
                case "s":
                    Map<String, Map<String, String>> skuFeatureMap = feature.getSkuFeature().getOrDefault(id, new HashMap<>());
                    skuFeatureMap.put(tableName, JSONUtils.fromJson(value, new TypeToken<Map<String, String>>() {
                    }, Collections.emptyMap()));
                    feature.getSkuFeature().put(id, skuFeatureMap);
                    break;
                case "o":
                    Map<String, Map<String, String>> otherFeatureMap = feature.getOtherFeature().getOrDefault(id, new HashMap<>());
                    otherFeatureMap.put(tableName, JSONUtils.fromJson(value, new TypeToken<Map<String, String>>() {
                    }, Collections.emptyMap()));
                    feature.getOtherFeature().put(id, otherFeatureMap);
                    break;
            }
        }

        return feature;
    }

    private FeatureKeyProto genWithCid(String table, String cid) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(cidUkFormat, table, cid))
                .setTableName(table)
                .putFieldValue("cid", cid)
                .build();
    }

    private FeatureKeyProto genWithAdVerId(String table, String adVerId) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(avUkFormat, table, adVerId))
                .setTableName(table)
                .putFieldValue("adverid", adVerId)
                .build();
    }

    private FeatureKeyProto genWithMidAndAdVerId(String table, String mid, String adVerId) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(avUkFormat, table, adVerId))
                .setTableName(table)
                .putFieldValue("mid", mid)
                .putFieldValue("adverid", adVerId)
                .build();
    }

    private FeatureKeyProto genWithMidAndSkuId(String table, String mid, String skuId) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(skuUkFormat, table, skuId))
                .setTableName(table)
                .putFieldValue("mid", mid)
                .putFieldValue("skuid", skuId)
                .build();
    }

    private FeatureKeyProto genWithCidAndRegion(String table, String cid, String region) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(cidUkDoubleFieldFormat, table, cid, region))
                .setTableName(table)
                .putFieldValue("cid", cid)
                .putFieldValue("region", region)
                .build();
    }

    private FeatureKeyProto genWithCidAndAppType(String table, String cid, String appType) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(cidUkDoubleFieldFormat, table, cid, appType))
                .setTableName(table)
                .putFieldValue("cid", cid)
                .putFieldValue("apptype", appType)
                .build();
    }

    private FeatureKeyProto genWithCidAndWeek(String table, String cid, String week) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(cidUkDoubleFieldFormat, table, cid, week))
                .setTableName(table)
                .putFieldValue("cid", cid)
                .putFieldValue("week", week)
                .build();
    }

    private FeatureKeyProto genWithCidAndHour(String table, String cid, String hour) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(cidUkDoubleFieldFormat, table, cid, hour))
                .setTableName(table)
                .putFieldValue("cid", cid)
                .putFieldValue("hour", hour)
                .build();
    }

    private FeatureKeyProto genWithCidAndBrand(String table, String cid, String brand) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(cidUkDoubleFieldFormat, table, cid, brand))
                .setTableName(table)
                .putFieldValue("cid", cid)
                .putFieldValue("brand", brand)
                .build();
    }

    private FeatureKeyProto genWithCidAndWechatVersion(String table, String cid, String wechatVersion) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(cidUkDoubleFieldFormat, table, cid, wechatVersion))
                .setTableName(table)
                .putFieldValue("cid", cid)
                .putFieldValue("wechatversion", wechatVersion)
                .build();
    }

    private FeatureKeyProto genWithCidAndVid(String table, String cid, String vid) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(cidUkDoubleFieldFormat, table, cid, vid))
                .setTableName(table)
                .putFieldValue("cid", cid)
                .putFieldValue("vid", vid)
                .build();
    }

    private FeatureKeyProto genWithVid(String table, String vid) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(vidUkFormat, table, vid))
                .setTableName(table)
                .putFieldValue("vid", vid)
                .build();
    }

    private FeatureKeyProto genWithMid(String table, String mid) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(midUkFormat, table, mid))
                .setTableName(table)
                .putFieldValue("mid", mid)
                .build();
    }

    private FeatureKeyProto genWithMidAndAppType(String table, String mid, String appType) {
        return FeatureKeyProto.newBuilder()
                .setUniqueKey(String.format(midUkFormat, table, mid))
                .setTableName(table)
                .putFieldValue("mid", mid)
                .putFieldValue("apptype", appType)
                .build();
    }

}
