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.ActionRequestValidationException;
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.Map;
import java.util.concurrent.*;


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

@Component
public class CalculateRFMTags {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

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

    @Autowired
    private TagsUtils tagsUtils;

    private static Integer nThreads = 20;

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

    /**
     * 并行计算RFM标签
     *
     * @throws SQLException
     */
    public void calculateRFMTags() throws Exception {

        // 获取标签列表
        HashMap<String, Map<String, Object>> RFMTagsAll = tagsUtils.getTagsById("9");

        // 获取RFM的中位数
        HashMap<String, String> hashMapMedian = getMedian();

        // 创建临时表
        createRFMTempTable(hashMapMedian.get("medianR"), hashMapMedian.get("medianF"), hashMapMedian.get("medianM"));

        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 = 5000;
                    logger.info(Thread.currentThread() + "开始");
                    for (int j = 0; j <= size / step; j++) {
                        insertIntoElasticSearch(RFMTagsAll, threadNumTemp, threadNumTemp * size + step * j, step);
                    }
                    logger.info("Thread:" + threadNumTemp + "计算RFM完成,time:" + System.currentTimeMillis());
                } catch (Exception e) {
                    e.printStackTrace();
                    logger.warn("Thread:" + threadNumTemp + "计算RFM失败,time:" + System.currentTimeMillis());
                } finally {
                    countDownLatch.countDown();
                }
            });
            Thread.sleep((long) (Math.random() * 5000 + 5000));
        }
        countDownLatch.await();
        System.out.println("--------结束-------------");
        // 删除临时表
        deleteRFMTempTable();
    }


    /**
     * 计算RFM的中位数
     *
     * @return
     * @throws SQLException
     */
    private HashMap<String, String> getMedian() throws SQLException {
        Connection connection = clickhouseDruidDataSourceMaster.getConnection();
        Statement clickhouseStatement = connection.createStatement();
        ResultSet resultSet = clickhouseStatement.executeQuery("SELECT median(R) as medianR, median(F) as medianF, " +
                "median(M) as medianM  from (SELECT AConsigneePhone2,  max(dateDiff('day', now(), OrderTime)) as R,sum(1.0) " +
                "as F, sum(Sum) as M from Orders where OrderStatus in(50,70,150) and IsReturn=0 AND OrdersCode not like '%BF' group by AConsigneePhone2)");
        resultSet.next();
        HashMap<String, String> hashMapMedian = new HashMap<>();
        hashMapMedian.put("medianR", resultSet.getString("medianR"));
        hashMapMedian.put("medianF", resultSet.getString("medianF"));
        hashMapMedian.put("medianM", resultSet.getString("medianM"));
        SqlUtils.close(connection, clickhouseStatement, resultSet);
        return hashMapMedian;
    }

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

    /**
     * 创建RFM临时表
     *
     * @param medianR
     * @param medianF
     * @param medianM
     */
    private void createRFMTempTable(String medianR, String medianF, String medianM) throws SQLException {
        Connection clickhouseConnection = clickhouseDruidDataSourceMaster.getConnection();
        Statement clickhouseStatement = clickhouseConnection.createStatement();
        clickhouseStatement.execute("CREATE TABLE IF NOT EXISTS  default.RFMTemp (`AConsigneePhone2` String, `R` Int16, `F` Int8, `M` Int64, `customerType` String) ENGINE = Memory");
        clickhouseStatement.execute("TRUNCATE TABLE default.RFMTemp");
        clickhouseStatement.execute("INSERT INTO RFMTemp SELECT AConsigneePhone2, R, F, M, caseWithExpression(toString(((RO * 4) + (FO * 2)) + MO), '7', '重要价值'," +
                " '6', '一般价值', '5', '重要发展', '3', '重要保持', '4', '一般发展', '2', '一般保持', '1', '重要挽留', '0', '一般挽留', '其他') " +
                "AS customerType FROM (SELECT AConsigneePhone2,  R,  F,  M,  multiIf(R > " +
                medianR + ", 1, 0) AS RO,  multiIf(F > " + medianF + ", 1, 0) AS FO,  multiIf(M > " + medianM + ", 1, 0) AS MO " +
                "FROM  (SELECT AConsigneePhone2, max(dateDiff('day', now(), OrderTime)) AS R, sum(1) AS F, sum(Sum) AS M FROM Orders" +
                " WHERE AConsigneePhone2 != '' and OrderStatus in(50,70,150) and IsReturn=0 AND OrdersCode not like '%BF' GROUP BY AConsigneePhone2));");
        clickhouseStatement.execute("INSERT INTO RFMTemp SELECT A.AConsigneePhone2, 0,0,0,'其他' as customerType from Orders A GLOBAL LEFT JOIN RFMTemp B on A.AConsigneePhone2 = B.AConsigneePhone2 where B.AConsigneePhone2='' and A.AConsigneePhone2 != '';");
        SqlUtils.close(clickhouseConnection, clickhouseStatement, null);
    }

    /**
     * 获取计算RFM的用户数
     *
     * @return 计算RFM的用户数
     * @throws SQLException
     */
    private Integer getCounts() throws SQLException {
        Connection clickhouseConnection = clickhouseDruidDataSourceMaster.getConnection();
        Statement clickhouseStatement = clickhouseConnection.createStatement();
        ResultSet resultSet = clickhouseStatement.executeQuery("SELECT COUNT(1) as counts from default.RFMTemp");
        resultSet.next();
        int counts = resultSet.getInt("counts");
        SqlUtils.close(clickhouseConnection, clickhouseStatement, resultSet);
        return counts;
    }


    /**
     * 将RFM标签数据插入ElasticSearch
     *
     * @param RFMTagsAll RFM标签
     * @param size
     * @throws SQLException
     * @throws IOException
     */
    private void insertIntoElasticSearch(HashMap<String, Map<String, Object>> RFMTagsAll, Integer threadId, Integer start, Integer size) throws Exception {

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

        ResultSet ordersRFM = clickhouseStatement.executeQuery("SELECT AConsigneePhone2, R, F, M, customerType from default.RFMTemp limit " + start + ", " + size);

        if(ordersRFM == null){
            return;
        }
        Integer step = 1000;
        Integer num = 0;

        RestHighLevelClient restHighLevelClient = ElasticSearchPoolUtil.getClient();

        BulkRequest bulkRequest = new BulkRequest().timeout(TimeValue.timeValueSeconds(180));
        bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);

        while (ordersRFM.next()) {
            String aConsigneePhone2 = ordersRFM.getString("AConsigneePhone2");
            Map<String, Object> hashMap = RFMTagsAll.get(ordersRFM.getString("customerType"));
            hashMap.put("R", ordersRFM.getDouble("R"));
            hashMap.put("F", ordersRFM.getDouble("F"));
            hashMap.put("M", ordersRFM.getDouble("M"));
            hashMap.put("TagCrtTime", DateUtils.getCurrentTime());

            // 查询Elasticsearch，获取该用户的信息
            GetResponse response = null;
            try{
                response  = restHighLevelClient.get(new GetRequest("crm_memberbase_phoneid_v1", aConsigneePhone2)
                        .fetchSourceContext(new FetchSourceContext(true, new String[]{"Tags"}, null)), RequestOptions.DEFAULT);
            }catch (ActionRequestValidationException e){
                e.printStackTrace();
                System.out.println(aConsigneePhone2);
            }

            ArrayList<Map<String, Object>> memberTags = new ArrayList<>();
            // 判断该用户是否存在，不存在则跳过
            if (response != null && response.isExists()) {
                if (response.getSourceAsMap().containsKey("Tags")) {
                    // 获取该用户的Tags字段
                    memberTags = (ArrayList<Map<String, Object>>) response.getSourceAsMap().get("Tags");
                }
                // 按isLastMonthCustomer和isBeforeCustomer计算该用户的复购金额和复购次数
                if (memberTags != null && memberTags.size() > 0) {
                    hashMap.put("RM_isLastMonthCustomer", memberTags.stream().filter(item -> "复购用户".equals(item.get("TagTypeName").toString()) && "T".equals(item.get("isLastMonthCustomer"))).mapToInt(item -> item.get("RM") == null ? 0 : (int) item.get("RM")).sum());
                    hashMap.put("RN_isLastMonthCustomer", memberTags.stream().filter(item -> "复购用户".equals(item.get("TagTypeName").toString()) && "T".equals(item.get("isLastMonthCustomer"))).mapToInt(item -> item.get("RN") == null ? 0 : (int) item.get("RN")).sum());
                    hashMap.put("RM_isBeforeCustomer", memberTags.stream().filter(item -> "复购用户".equals(item.get("TagTypeName").toString()) && "T".equals(item.get("isBeforeCustomer"))).mapToInt(item -> item.get("RM") == null ? 0 : (int) item.get("RM")).sum());
                    hashMap.put("RN_isBeforeCustomer", memberTags.stream().filter(item -> "复购用户".equals(item.get("TagTypeName").toString()) && "T".equals(item.get("isBeforeCustomer"))).mapToInt(item -> item.get("RN") == null ? 0 : (int) item.get("RN")).sum());
                    memberTags.removeIf(item -> hashMap.get("TagTypeName").equals(item.get("TagTypeName").toString()));
                }
                // 将RFM标签加入到Tags中
                memberTags.add(hashMap);
            } 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) {
                if (bulkRequest.numberOfActions() > 0) {
                    try {
                        restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
                    } catch (SocketTimeoutException e) {
                        restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
                    }
                }
                logger.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);
                }
            }
            logger.info("线程" + threadId + "插入了： " + num + " 条数据");
        }
        logger.info("线程" + threadId + "总共插入了： " + num + " 条数据");

        SqlUtils.close(clickhouseConnection, clickhouseStatement, null);
        ElasticSearchPoolUtil.returnClient(restHighLevelClient);

    }
}
