package com.zm.order.tool.server.service.impl.routing;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.zm.order.tool.server.dao.OrderPayDetailsDao;
import com.zm.order.tool.server.manager.CommonManager;
import com.zm.order.tool.server.manager.NewHintManager;
import com.zm.order.tool.server.manager.RedisManager;
import com.zm.order.tool.server.manager.RoutingAlgorithm;
import com.zm.order.tool.server.model.OrderNoUser;
import com.zm.order.tool.server.model.OrderPayDetails;
import com.zm.order.tool.server.query.OrderPayDetailsQuery;
import com.zm.order.tool.server.service.PayDetailsRoutingService;
import com.zm.order.tool.server.tempdb.OrderNoUserDao;
import com.zm.order.tool.server.utils.DateUtils;
import com.zm.util.dto.Result;
import org.apache.lucene.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author nianchang
 * @date 2022/01/25 3:57 下午
 */
@Service
@SuppressWarnings("all")
public class PayDetailsRoutingServiceImpl implements PayDetailsRoutingService, InitializingBean {

    private static final Logger logger = LoggerFactory.getLogger(PayDetailsRoutingServiceImpl.class);

    private BlockingQueue workQueue;

    private ExecutorService executorService;

    @Value("${new.sharding.db.size}")
    private int dbSize;

    @Value("${new.sharding.tb.size}")
    private int tbSize;

    @Value("${sharding.db.size}")
    private int oldDbSize;

    @Value("${sharding.tb.size}")
    private int oldTbSize;

    @Autowired
    private RedisManager redisManager;

    @Autowired
    private NewHintManager hintManager;

    @Autowired
    private CommonManager commonManager;

    @Override
    public void routing() {
        int count = 0;
        OrderPayDetailsQuery query = new OrderPayDetailsQuery();
//        query.setExcludeSellerId(40927L);
//        query.setUpdatedAtEnd(new Date(1644422400000L));
        query.setSize(200);
        try {
            for (int i = 0; i < oldDbSize; i++) {
                for (int j = 0; j < oldTbSize; j++) {
                    Long lastId = null;
                    int loop = 0;
                    Map<String, List<OrderPayDetails>> map = new HashMap<>(262144);
                    for (;;) {
                        query.setLastId(lastId);
                        long startAt = System.currentTimeMillis();
                        List<OrderPayDetails> list = commonManager.scanPayDetailsList(query, i, j);
                        long endAt = System.currentTimeMillis() - startAt;
                        if (endAt >= 500) {
                            logger.info("pay_details_routing_query_time:{}ms", endAt);
                        }
                        if (CollectionUtils.isEmpty(list)) {
                            if (!CollectionUtils.isEmpty(map)) {
                                map.forEach((k, v) -> execute(k, v));
                            }
                            break;
                        }
                        for (OrderPayDetails record : list) {
                            groupOrder(record, map);
                        }
                        if (++loop >= 100) {
                            map.forEach((k, v) -> execute(k, v));
                            map.clear();
                            loop = 0;
                        }
                        count += list.size();
                        logger.info("pay_details_routing_count:{}条", count);
                        if (list.size() < 200) {
                            if (!CollectionUtils.isEmpty(map)) {
                                map.forEach((k, v) -> execute(k, v));
                            }
                            break;
                        }
                        lastId = list.get(list.size() - 1).getId();
                        logger.info("pay_details_routing_last_id:{},i:{},j:{}", lastId, i, j);
                        Result<String> redisData = redisManager.get("routing_pay_details_shut_down_key");
                        if (redisData.isStatus() && Objects.nonNull(redisData.getData())) {
                            logger.info("[PAY_DETAILS_ROUTING]shut down！");
                            return;
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("[PayDetailsRoutingServiceImpl]failed!cause by:{0}", e);
            throw new RuntimeException("系统异常");
        }
    }

    private void execute(String k, List<OrderPayDetails> list) {
        while (workQueue.remainingCapacity() < 1) {
            logger.info("workQueue size:{}", workQueue.size());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executorService.submit(() -> {
            try {
                String[] split = k.split("-");
                for (List<OrderPayDetails> subList : Lists.partition(list, 50)) {
                    hintManager.replacePayDetails(subList, Integer.valueOf(split[0]), Integer.valueOf(split[1]));
                }
            } catch (Exception e) {
                logger.error("[execute]failed！cause by:{}", e);
            }
        });
    }

    private void groupOrder(OrderPayDetails record, Map<String, List<OrderPayDetails>> map) {
        // 计算在新数据库中的路由
        final Long userId = record.getUserId();
        int dbIndex = RoutingAlgorithm.routeDB(userId, dbSize);
        int tbIndex = RoutingAlgorithm.routeTB(userId, dbSize, tbSize);
        // 按照路由结果进行分组，用于批量查询
        String key = dbIndex + "-" + tbIndex;
        List<OrderPayDetails> list = null;
        if (!map.containsKey(key)) {
            list = new ArrayList<>();
        } else {
            list = map.get(key);
        }
        list.add(record);
        map.put(key, list);
    }

    @Override
    public void check() {
        int count = 0;
        Date now = new Date();
        AtomicInteger diffCount = new AtomicInteger();
        AtomicInteger same = new AtomicInteger();
        OrderPayDetailsQuery query = new OrderPayDetailsQuery();
        query.setMaxId(716440803752341506L);
//        query.setExcludeSellerId(40927L);
//        query.setUpdatedAtStart(DateUtils.before(now, Calendar.DAY_OF_YEAR, 30));
//        query.setUpdatedAtEnd(now);
        query.setSize(1000);
        try {
            for (int i = 0; i < oldDbSize; i++) {
                for (int j = 0; j < oldTbSize; j++) {
                    Long lastId = 697264332513206272L;
                    int loop = 0;
                    Map<String, List<OrderPayDetails>> map = new HashMap<>(262144);
                    for (;;) {
                        query.setLastId(lastId);
                        List<OrderPayDetails> dataList = commonManager.scanPayDetailsList(query, i, j);
                        if (CollectionUtils.isEmpty(dataList)) {
                            if (!CollectionUtils.isEmpty(map)) {
                                map.forEach((k, v) -> execute0(k, v, diffCount, same));
                            }
                            break;
                        }
                        for (OrderPayDetails record : dataList) {
                            groupOrder(record, map);
                        }
                        if (++loop >= 100) {
                            map.forEach((k, v) -> execute0(k, v, diffCount, same));
                            map.clear();
                            loop = 0;
                        }
                        count += dataList.size();
                        logger.info("PAY_DETAILS_CHECK_COUNT:{}条", count);
                        if (dataList.size() < 1000) {
                            if (!CollectionUtils.isEmpty(map)) {
                                map.forEach((k, v) -> execute0(k, v, diffCount, same));
                            }
                            break;
                        }
                        lastId = dataList.get(dataList.size() - 1).getId();
                        logger.info("pay_details_check_last_id:{},i:{},j:{}", lastId, i, j);

                        Result<String> redisData = redisManager.get("check_pay_details_shut_down_key");
                        if (redisData.isStatus() && Objects.nonNull(redisData.getData())) {
                            logger.info("[PAY_DETAILS_CHECK]shut down！");
                            return;
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("[PayDetailsRoutingServiceImpl]check failed!cause by:{0}", e);
            throw new RuntimeException("系统异常");
        }
    }

    private void execute0(String k, List<OrderPayDetails> list, AtomicInteger diffCount, AtomicInteger same) {
        while (workQueue.remainingCapacity() < 20) {
            logger.info("workQueue size:{}", workQueue.size());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executorService.submit(() -> {
            try {
                String[] split = k.split("-");
                int dbIndex = Integer.valueOf(split[0]);
                int tbIndex = Integer.valueOf(split[1]);
                List<Long> idList = list.stream().map(OrderPayDetails::getId).collect(Collectors.toList());
                List<OrderPayDetails> newList = hintManager.selectPayDetailsById(idList, dbIndex, tbIndex);
                this.compare(list, newList, diffCount,same);
                logger.info("COMPARE_PAY_DETAILS_COUNT, diffCount:{}条, sameCount:{}条", diffCount, same);
            } catch (Exception e) {
                logger.error("[execute0]failed！cause by:{}", e);
            }
        });
    }

    @Autowired
    private OrderPayDetailsDao payDetailsDao;

    private void compare(List<OrderPayDetails> oldList, List<OrderPayDetails> newList, AtomicInteger diffCount, AtomicInteger same) {
        Map<Long, List<OrderPayDetails>> newMap = newList.stream().collect(Collectors.groupingBy(OrderPayDetails::getId));
        for (OrderPayDetails record : oldList) {
            List<OrderPayDetails> dataList = newMap.get(record.getId());
            if (CollectionUtils.isEmpty(dataList)) {
                logger.info("PAY_DETAIL_EMPTY, orderNo:{}", record.getOrderNo());
                payDetailsDao.updateUpdatedAt(record.getId(), record.getUserId());
                continue;
            }
            String oldData = JSON.toJSONString(record);
            String newData = JSON.toJSONString(dataList.get(0));
            if (!Objects.equals(newData, oldData)) {
                OrderNoUser noUser = buildParam(record);
                noUserDao.insert(noUser);
                diffCount.incrementAndGet();
                logger.info("PAY_DETAILS_FIND_DIFF_ORDER,old:{},new:{}", oldData, newData);
            } else {
                same.incrementAndGet();
            }
        }
    }

    @Override
    public void updateData() {
        int count = 0;
        Date now = new Date();
        OrderPayDetailsQuery query = new OrderPayDetailsQuery();
        query.setUpdatedAtStart(DateUtils.before(now, Calendar.DAY_OF_YEAR, 7));
        query.setUpdatedAtEnd(now);
        query.setSize(1000);
        try {
            for (int i = 0; i < oldDbSize; i++) {
                for (int j = 0; j < oldTbSize; j++) {
                    Long lastId = null;
                    for (;;) {
                        query.setLastId(lastId);
                        List<Long> idList = commonManager.scanPayDetailsIdList(query, i, j);
                        if (CollectionUtils.isEmpty(idList)) {
                            break;
                        }
                        execute1(idList, i, j);
                        count += idList.size();
                        logger.info("pay_details_update_data_count:{}条", count);
                        if (idList.size() < 1000) {
                            break;
                        }
                        lastId = idList.get(idList.size() - 1);
                        logger.info("pay_details_update_data_last_id:{},i:{},j:{}", lastId, i, j);
                        Result<String> redisData = redisManager.get("update_data_pay_details_shut_down_key");
                        if (redisData.isStatus() && Objects.nonNull(redisData.getData())) {
                            logger.info("[PAY_DETAILS_UPDATE_DATA]shut down！");
                            return;
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("[DiscountRoutingServiceImpl]failed!cause by:{0}", e);
            throw new RuntimeException("系统异常");
        }
    }

    private void execute1(List<Long> idList, int i, int j) {
        while (workQueue.remainingCapacity() < 20) {
            logger.info("workQueue size:{}", workQueue.size());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executorService.submit(() -> {
            try {
                commonManager.payDetailsBatchUpdateDate(idList, i, j);
            } catch (Exception e) {
                logger.error("[execute]failed！cause by:{}", e);
            }
        });
    }

    @Autowired
    private OrderNoUserDao noUserDao;

//    @Scheduled(fixedDelay = 60000)
    public void discountCheckTask() {
        int count = 0;
        OrderPayDetailsQuery query = buildQuery();
        AtomicInteger diff = new AtomicInteger();
        AtomicInteger same = new AtomicInteger();
        try {
            for (int i = 0; i < oldDbSize; i++) {
                for (int j = 0; j < oldTbSize; j++) {
                    List<OrderPayDetails> list = commonManager.scanPayDetailsList(query, i, j);
                    if (CollectionUtils.isEmpty(list)) {
                        break;
                    }
                    list.forEach(o -> compare0(o, diff, same));
                }
            }
            logger.info("COMPARE0_PAY_DETAIL_COUNT, diffCount:{}条, sameCount:{}条", diff, same);
        } catch (Exception e) {
            logger.error("[PAY_DETAIL_CHECK_TASK]failed! cause by:{0}", e);
        }
    }

    private void compare0(OrderPayDetails oldData, AtomicInteger diff, AtomicInteger same) {
        List<OrderPayDetails> newList = this.getNewList(oldData);
        if (CollectionUtils.isEmpty(newList)) {
            return;
        }
        String oldNode = JSON.toJSONString(oldData);
        String newNode = JSON.toJSONString(newList.get(0));
        if (!Objects.equals(newNode, oldNode)) {
            OrderNoUser noUser = buildParam(oldData);
            noUserDao.insert(noUser);
            diff.incrementAndGet();
            logger.info("FIND_DIFF_PAY_DETAIL0, old:{}, new:{}", oldNode, newNode);
        } else {
            same.incrementAndGet();
        }
    }

    private OrderNoUser buildParam(OrderPayDetails record) {
        OrderNoUser noUser = new OrderNoUser();
        noUser.setOrderNo(record.getOrderNo());
        noUser.setUserId(record.getUserId());
        noUser.setOrderId(record.getId());
        noUser.setType(4);
        return noUser;
    }

    private List<OrderPayDetails> getNewList(OrderPayDetails oldNode) {
        final Long userId = oldNode.getUserId();
        int dbIndex = RoutingAlgorithm.routeDB(userId, dbSize);
        int tbIndex = RoutingAlgorithm.routeTB(userId, dbSize, tbSize);
        return hintManager.selectPayDetailsById(Lists.newArrayList(oldNode.getId()), dbIndex, tbIndex);
    }

    private OrderPayDetailsQuery buildQuery() {
        Date nowDate = new Date();
        Date beforeDate0 = DateUtils.before(nowDate, Calendar.MINUTE, 3);
        Date beforeDate1 = DateUtils.before(nowDate, Calendar.MINUTE, 5);
        OrderPayDetailsQuery query = new OrderPayDetailsQuery();
        query.setOrderAtEnd(beforeDate0);
        query.setOrderAtStart(beforeDate1);
        query.setSize(1000);
        return query;
    }

    @Override
//    @Scheduled(cron = "0 0 4 * * ?")
    public void checkByOne() {
        int count = 0;
        Date now = new Date();
        AtomicInteger diffCount = new AtomicInteger();
        AtomicInteger same = new AtomicInteger();
        OrderPayDetailsQuery query = new OrderPayDetailsQuery();
        query.setSortFlag(1);
//        query.setUpdatedAtEnd(new Date(1652112000000L));
//        query.setUpdatedAtStart(new Date(1652104800000L));
//        query.setUpdatedAtEnd(new Date(1652117400000L));
//        query.setUpdatedAtStart(new Date(1652112000000L));

        query.setUpdatedAtEnd(new Date(1652119200000L));
        query.setUpdatedAtStart(new Date(1652117400000L));
        query.setPageSize(1000);
        try {
            for (int i = 0; i < oldDbSize; i++) {
                for (int j = 0; j < oldTbSize; j++) {
                    Long lastId = null;
                    int loop = 0;
                    int page = 1;
                    Map<String, List<OrderPayDetails>> map = new HashMap<>(262144);
                    for (;;) {
                        query.setPageNo(page);
                        List<OrderPayDetails> dataList = commonManager.scanPayDetailsList(query, i, j);
                        if (CollectionUtils.isEmpty(dataList)) {
                            if (!CollectionUtils.isEmpty(map)) {
                                map.forEach((k, v) -> execute0(k, v, diffCount, same));
                            }
                            break;
                        }
                        for (OrderPayDetails record : dataList) {
                            groupOrder(record, map);
                        }
                        if (++loop >= 100) {
                            map.forEach((k, v) -> execute0(k, v, diffCount, same));
                            map.clear();
                            loop = 0;
                        }
                        page++;
                        count += dataList.size();
                        logger.info("PAY_DETAILS_CHECK_BY_ONE_COUNT:{}条", count);
                        if (dataList.size() < 1000) {
                            if (!CollectionUtils.isEmpty(map)) {
                                map.forEach((k, v) -> execute0(k, v, diffCount, same));
                            }
                            break;
                        }
                        lastId = dataList.get(dataList.size() - 1).getId();
                        logger.info("pay_details_check_by_one_last_id:{},i:{},j:{}", lastId, i, j);

                        Result<String> redisData = redisManager.get("check_pay_details_shut_down_key");
                        if (redisData.isStatus() && Objects.nonNull(redisData.getData())) {
                            logger.info("[PAY_DETAILS_CHECK]shut down！");
                            return;
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("[PayDetailsRoutingServiceImpl]check failed!cause by:{0}", e);
            throw new RuntimeException("系统异常");
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        int coreSize = Runtime.getRuntime().availableProcessors();
        workQueue = new ArrayBlockingQueue(128);
        executorService = new ThreadPoolExecutor(20, 20, 1, TimeUnit.MINUTES,
                workQueue, new NamedThreadFactory("order-routing"),
                new ThreadPoolExecutor.AbortPolicy());
    }
}
