package com.example.repurchase.getTags;


import com.example.repurchase.configuration.ElasticSearchPoolUtil;
import com.example.repurchase.utils.DateUtils;
import com.example.repurchase.utils.SqlUtils;
import com.example.repurchase.utils.TagsUtils;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.*;


/**
 * @author 黄树文
 * @date 2020/2/25 14:31
 */

@Component
public class CalculateRepurchaseUserTags {


    private Logger log = LoggerFactory.getLogger(CalculateRepurchaseUserTags.class);

    @Resource(name = "clickhouseDruidDataSourceMaster")
    private DataSource clickhouseDruidDataSourceMaster;

    @Autowired
    private TagsUtils tagsUtils;


    private static Integer nThreads = 10;

    private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(nThreads);

    /**
     * 计算复购用户信息，插入ElasticSearch
     *
     * @param yearAndMonth 月份 格式为yyyy-MM 2020-02
     * @throws SQLException
     * @throws IOException
     */
    // get the repurchace user info by month and originType
    public void getRepurchaseUserByMonth(String yearAndMonth) throws Exception {

        String thisYearAndMonth = DateUtils.getOffsetMonth(yearAndMonth, 0);

        // 获取所有的复购标签
        HashMap<String, Map<String, Object>> repurchaseTagsAll = tagsUtils.getTagsById("10");

        // 获取复购用户，本月之前有购买记录的用户
        HashSet<String> repurchaseUserBefore = getRepurchaseUserAConsigneePhone2(thisYearAndMonth, false);
        // 获取复购用户，上月有购买记录的用户
        HashSet<String> repurchaseUserLastMonth = getRepurchaseUserAConsigneePhone2(thisYearAndMonth, true);
        // 获取本月复购用户的标签
        Map<String, Object> repurchaseTagOfThisMonth = repurchaseTagsAll.get(yearAndMonth.replaceAll("-|/", ""));

        // 创建复购率计算临时表
        createRepurchaseTempTable(yearAndMonth);

        Integer counts = getCounts();

        Integer size = (new Double(Math.ceil(counts / (nThreads + 0.0)))).intValue();
        CountDownLatch countDownLatch = new CountDownLatch(nThreads);

        for (int i = 0; i < nThreads; i++) {
            final int threadNumTemp = i;
            fixedThreadPool.execute(() -> {
                try {
                    Integer step = 10000;
                    for (int j = 0; j <= size / step; j++) {
                        insertToEs(repurchaseTagOfThisMonth, repurchaseUserLastMonth, repurchaseUserBefore, threadNumTemp, threadNumTemp * size + step * j, step);
                    }
                    log.info("Thread:" + threadNumTemp + "计算复购率完成,time:" + System.currentTimeMillis());
                } catch (Exception e) {
                    e.printStackTrace();
                    log.warn("Thread:" + threadNumTemp + "计算复购率失败,time:" + System.currentTimeMillis());
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        deleteRepurchaseTempTable();
        log.info("结束");
    }

    /**
     * 删除临时表
     */
    private void deleteRepurchaseTempTable() throws SQLException {
        Connection clickhouseConnection = clickhouseDruidDataSourceMaster.getConnection();
        Statement clickhouseStatement = clickhouseConnection.createStatement();
        clickhouseStatement.execute("DROP TABLE `default`.repurchaseTemp;");
        clickhouseConnection.commit();
        SqlUtils.close(clickhouseConnection, clickhouseStatement, null);
    }

    /**
     * 创建复购率计算临时表
     *
     * @param yearAndMonth
     */
    private void createRepurchaseTempTable(String yearAndMonth) throws SQLException {
        Connection clickhouseConnection = clickhouseDruidDataSourceMaster.getConnection();
        Statement clickhouseStatement = clickhouseConnection.createStatement();
        clickhouseStatement.execute("CREATE TABLE IF NOT EXISTS  default.repurchaseTemp (`AConsigneePhone2` String, `OriginType` Array(Int8), `RM` Array(Int32), `RN` Array(Int16)) ENGINE = Memory;");
        clickhouseStatement.execute("TRUNCATE TABLE default.repurchaseTemp");
        clickhouseStatement.execute("INSERT INTO `default`.repurchaseTemp SELECT AConsigneePhone2, groupArray(OriginType) as OriginType, groupArray(RM) as RM, groupArray(RN) as RN from (select AConsigneePhone2, OriginType, sum(Sum) as RM, count(AConsigneePhone2) as RN from Orders where OrderStatus  in(50,70,150) and IsReturn=0 AND OrdersCode not like '%BF' and toYYYYMM(OrderTime) = " + yearAndMonth.replaceAll("-", "") + " group by AConsigneePhone2, OriginType) GROUP by AConsigneePhone2 ORDER BY AConsigneePhone2;");
        SqlUtils.close(clickhouseConnection, clickhouseStatement, null);
    }

    /**
     * 将复购用户数据插入到ElasticSearch
     *
     * @param repurchaseTagOfThisMonth 本月的复购标签 为一个hashMap，里面包含标签的各种信息
     * @param repurchaseUserLastMonth  上月有购买行为的用户
     * @param repurchaseUserBefore     本月之前有购买行为的用户
     * @param size
     * @throws SQLException
     * @throws IOException
     */
    private void insertToEs(Map<String, Object> repurchaseTagOfThisMonth, HashSet<String> repurchaseUserLastMonth, HashSet<String> repurchaseUserBefore, Integer threadId, Integer start, Integer size) throws Exception {

        Connection clickhouseConnection = clickhouseDruidDataSourceMaster.getConnection();
        Statement clickhouseStatement = clickhouseConnection.createStatement();

        ResultSet repurchaseUserByMonth = clickhouseStatement.executeQuery("SELECT AConsigneePhone2, OriginType, RM, RN from `default`.repurchaseTemp limit " + start + ", " + size);

        Integer step = 1000;
        Integer num = 0;

        RestHighLevelClient restHighLevelClient = ElasticSearchPoolUtil.getClient();

        BulkRequest bulkRequest = new BulkRequest().timeout(TimeValue.timeValueMillis(2));
        bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);

        while (repurchaseUserByMonth.next()) {

            String aConsigneePhone2 = repurchaseUserByMonth.getString("AConsigneePhone2");

            // 跳过AConsigneePhone2为空值的数据
            if ("".equals(aConsigneePhone2)) {
                continue;
            }

            // 解析repurchaseUserByMonth
            String OriginType = repurchaseUserByMonth.getString("OriginType");
            String RM = repurchaseUserByMonth.getString("RM");
            String RN = repurchaseUserByMonth.getString("RN");
            String[] OriginTypes = OriginType.replaceAll("\\[|\\]", "").split(",");
            String[] RMs = RM.replaceAll("\\[|\\]", "").split(",");
            String[] RNs = RN.replaceAll("\\[|\\]", "").split(",");
            // 各个平台的复购信息写入originTypeHashMaps
            ArrayList<HashMap<String, Object>> originTypeHashMaps = new ArrayList<>();
            for (int i = 0; i < OriginTypes.length; i++) {
                HashMap<String, Object> originTypeHashMap = new HashMap<>();
                originTypeHashMap.put("OriginType", OriginTypes[i]);
                originTypeHashMap.put("RM", Integer.parseInt(RMs[i]));
                originTypeHashMap.put("RN", Integer.parseInt(RNs[i]));
                originTypeHashMaps.add(originTypeHashMap);
            }
            // 计算所有平台复购金额（rm）和复购次数（rn）之和
            int rm = originTypeHashMaps.stream().mapToInt(item -> Integer.parseInt(item.get("RM").toString())).sum();
            int rn = originTypeHashMaps.stream().mapToInt(item -> Integer.parseInt(item.get("RN").toString())).sum();

            // 将用户的复购信息与复购标签合并，存入repurchaseTagOfThisCustomer
            Map<String, Object> repurchaseTagOfThisCustomer = repurchaseTagOfThisMonth;

            repurchaseTagOfThisCustomer.put("OriginTypeRepur", originTypeHashMaps);
            repurchaseTagOfThisCustomer.put("RM", rm);
            repurchaseTagOfThisCustomer.put("RN", rn);
            repurchaseTagOfThisCustomer.put("TagCrtTime", DateUtils.getCurrentTime());

            // 判断用户上月是否有购买行为，有则添加字段isLastMonthCustomer：T否则isLastMonthCustomer：F
            if (repurchaseUserLastMonth.contains(aConsigneePhone2)) {
                repurchaseTagOfThisCustomer.put("isLastMonthCustomer", "T");
            } else {
                repurchaseTagOfThisCustomer.put("isLastMonthCustomer", "F");
            }
            // 判断用户本月之前是否有购买行为，有则添加字段isLastMonthCustomer：T否则isLastMonthCustomer：F
            if (repurchaseUserBefore.contains(aConsigneePhone2)) {
                repurchaseTagOfThisCustomer.put("isBeforeCustomer", "T");
            } else {
                repurchaseTagOfThisCustomer.put("isBeforeCustomer", "F");
            }
            // 读取ElasticSearch该用户的所有信息
            GetResponse response = restHighLevelClient.get(new GetRequest("crm_memberbase_phoneid_v1", aConsigneePhone2)
                    .fetchSourceContext(new FetchSourceContext(true, new String[]{"Tags"}, null)), RequestOptions.DEFAULT);

            ArrayList<Map<String, Object>> memberTags = new ArrayList<>();
            // 判断ElasticSearch中是否有该用户的信息，如果该用户不存在，则跳过
            if (response.isExists()) {
                if (response.getSourceAsMap().containsKey("Tags")) {
                    memberTags = (ArrayList<Map<String, Object>>) response.getSourceAsMap().get("Tags");
                    Map<String, Object> finalHashmap = repurchaseTagOfThisCustomer;
                    // 删除TagName一样的数据
                    if (memberTags != null && memberTags.size() > 0) {
                        memberTags.removeIf(item -> finalHashmap.get("TagName").equals(item.get("TagName").toString()));
                    }
                }
                // 将复购标签添加至用户标签数组中
                memberTags.add(repurchaseTagOfThisCustomer);
            } else {
                continue;
            }

            UpdateRequest tagsUpdateRequest = new UpdateRequest("crm_memberbase_phoneid_v1", aConsigneePhone2).fetchSource("Tags", null)
                    .doc(XContentType.JSON, "Tags", memberTags).upsert();
            tagsUpdateRequest.retryOnConflict(5);

            bulkRequest.add(tagsUpdateRequest);

            num++;

            if (num % step == 0) {
                log.info(aConsigneePhone2 + repurchaseTagOfThisCustomer);
                if (bulkRequest.numberOfActions() > 0) {
                    try {
                        restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
                    } catch (SocketTimeoutException e) {
                        restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
                    }
                }
                log.info("线程" + threadId + "插入了： " + num + " 条数据");
                bulkRequest = new BulkRequest().timeout(TimeValue.timeValueMillis(2));
            }
        }
        if (num % step != 0) {
            if (bulkRequest.numberOfActions() > 0) {
                try {
                    restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
                } catch (SocketTimeoutException e) {
                    restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
                }
            }
            log.info("线程" + threadId + "共插入了： " + num + " 条数据");
        }
        log.info("线程" + threadId + "总共插入了： " + num + " 条数据");
        SqlUtils.close(clickhouseConnection, clickhouseStatement, null);
        ElasticSearchPoolUtil.returnClient(restHighLevelClient);
    }

    /**
     * 获取复购的用户
     *
     * @param yearAndMonth 月份 格式为yyyy-MM 2020-02
     * @param isLastMonth  用户是否在上个月有购买行为
     * @return 复购用户的电话号码AConsigneePhone2
     * @throws SQLException
     */
    private HashSet<String> getRepurchaseUserAConsigneePhone2(String yearAndMonth, boolean isLastMonth) throws SQLException {
        Connection clickhouseConnection = clickhouseDruidDataSourceMaster.getConnection();
        Statement clickhouseStatement = clickhouseConnection.createStatement();

        yearAndMonth = yearAndMonth.replaceAll("-", "");
        String lastYearAndMonth = String.valueOf(Integer.parseInt(yearAndMonth) - 1);
        HashSet<String> repurchaseUser = new HashSet<>();
        ResultSet resultSet = null;
        try {
            if (isLastMonth) {
                resultSet = clickhouseStatement.executeQuery("select distinct AConsigneePhone2 from Orders where OrderStatus in(50,70,150) and IsReturn=0 AND OrdersCode not like '%BF' and toYYYYMM(OrderTime) = " + yearAndMonth + " and AConsigneePhone2 global in (SELECT DISTINCT AConsigneePhone2 FROM Orders WHERE AConsigneePhone2 != '' and OrderStatus  in(50,70,150) and IsReturn=0 AND OrdersCode not like '%BF' and  toYYYYMM(OrderTime) = " + lastYearAndMonth + ")");
            } else {
                resultSet = clickhouseStatement.executeQuery("select distinct AConsigneePhone2 from Orders where OrderStatus in(50,70,150) and IsReturn=0 AND OrdersCode not like '%BF' and toYYYYMM(OrderTime) = " + yearAndMonth + " and AConsigneePhone2 global in (SELECT DISTINCT AConsigneePhone2 FROM Orders WHERE AConsigneePhone2 != '' and OrderStatus  in(50,70,150) and IsReturn=0 AND OrdersCode not like '%BF' and toYYYYMM(OrderTime) < " + yearAndMonth + ")");
            }
            while (resultSet.next()) {
                repurchaseUser.add(resultSet.getString("AConsigneePhone2"));
            }
        } catch (Exception e) {
            SqlUtils.close(clickhouseConnection, clickhouseStatement, resultSet);
            return getRepurchaseUserAConsigneePhone2(yearAndMonth, isLastMonth);
        }
        SqlUtils.close(clickhouseConnection, clickhouseStatement, resultSet);
        return repurchaseUser;
    }

    /**
     * 获取计算RFM的用户数
     *
     * @return 计算RFM的用户数
     * @throws SQLException
     */
    private Integer getCounts() throws SQLException {

        Connection clickhouseConnection = clickhouseDruidDataSourceMaster.getConnection();
        Statement clickhouseStatement = clickhouseConnection.createStatement();
        String sql = "SELECT COUNT(1) as counts from `default`.repurchaseTemp;";
        ResultSet resultSet = clickhouseStatement.executeQuery(sql);
        resultSet.next();
        int counts = resultSet.getInt("counts");
        SqlUtils.close(clickhouseConnection, clickhouseStatement, resultSet);
        return counts;

    }
}
