package com.sifang.modules.taskdispatch.service.autod;


import com.jfinal.plugin.activerecord.Record;
import com.sifang.modules.flowable.dao.ItemManageDao;
import com.sifang.modules.sunshine.SystemSetting.utils.BusinessException;
import com.sifang.modules.sys.util.SpringUtils;
import com.sifang.modules.taskdispatch.constant.ReviewDispatchConst;
import com.sifang.modules.taskdispatch.dao.CrmReviewGroupMemberDao;
import com.sifang.modules.taskdispatch.service.SimpleLock;
import com.sifang.modules.taskdispatch.service.autod.vo.ItemTask;
import com.sifang.modules.taskdispatch.service.autod.vo.ItemTaskWorker;
import com.sifang.modules.taskdispatch.util.ItemTaskSignal;
import com.sifang.modules.taskdispatch.util.ReviewGroupUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

public class GroupDispatchRunner implements Runnable {

    private final List<GroupItemManager> groupItemManagers;
    private final String partnerId;
    private static final Logger log = LoggerFactory.getLogger(GroupDispatchRunner.class);
    private final ItemTaskSignal signal;
//    private final Object dispatchLock = new Object();

    private static final String FAIL_REASON_NO_TASK = "no_more_task";
    private static final String FAIL_REASON_TASK_PIN = "take_pinned_to_a_prefer_user";

    public GroupDispatchRunner(String partnerId, List<GroupItemManager> groupItemManagers, ItemTaskSignal signal) {
        this.groupItemManagers = groupItemManagers;
        this.partnerId = partnerId;
        this.signal = signal;
    }

    @Override
    public void run() {
        while (true) {
            signal.consume();
            try {
                // query online members of this group;
                CrmReviewGroupMemberDao memberDao = SpringUtils.getBean(CrmReviewGroupMemberDao.class);
                List<Record> freeWorkers = memberDao.findFreeOnlineWorkers(this.partnerId);
                doRun(freeWorkers);
            } catch (Exception ex) {
                log.error("dispatch runner error", ex);
            }
        }
    }

    public void dispatch(List<String> userIds) {
        CrmReviewGroupMemberDao memberDao = SpringUtils.getBean(CrmReviewGroupMemberDao.class);
        List<Record> freeWorkers = memberDao.findFreeOnlineWorkers(this.partnerId, userIds);
        doRun(freeWorkers);
    }

    public void doRun(List<Record> freeWorkers) {
        long t = System.currentTimeMillis();
        DispatchOrders dispatchOrders = new StarveDispatchOrders();
        freeWorkers = dispatchOrders.order(freeWorkers);
        for (Record freeWorker : freeWorkers) {
            for (GroupItemManager itemManager : groupItemManagers) {
                if (Objects.equals(itemManager.getGroupId(), freeWorker.getStr("group_id"))) {
                    dispatch2FreeWorker(freeWorker, itemManager, true);
                }
            }
        }
        log.info("end dispatch for parnter: {}, consumed: {} ms", this.partnerId, System.currentTimeMillis() - t);
    }

    public void dispatch2FreeWorker(Record freeWorker, GroupItemManager itemManager, boolean preferCheck) {
        Set<ItemTask> returnTasks = new HashSet<>();
        try {
            ItemTaskWorker worker = asItemTaskWorker(freeWorker);
            log.info("资方:{}, 给组: {} 员工：{} 派单", this.partnerId, itemManager.getGroupId(), worker.getName());
            tryDispatchLocked(worker, itemManager, returnTasks, freeWorker.get("groups"), preferCheck);
        } finally {
            for (ItemTask returnTask : returnTasks) {
                itemManager.returnTask(returnTask);
            }
        }
    }

    private ItemTaskWorker asItemTaskWorker(Record freeWorker) {
        ItemTaskWorker worker = new ItemTaskWorker();
        worker.setEmployeeId(freeWorker.getStr("employee_id"));
        worker.setMemberId(freeWorker.getStr("id"));
        worker.setProvinces(freeWorker.getStr("provices"));
        worker.setCity(freeWorker.getStr("city"));
        worker.setUserId(freeWorker.getStr("user_id"));
        worker.setName(freeWorker.getStr("name"));
        worker.setGroupId(freeWorker.getStr("group_id"));
        return worker;
    }

    private void tryDispatch(ItemTaskWorker freeWorker, GroupItemManager itemManager, Set<ItemTask> returnTasks) {
        while (true) {
            try {
                log.info("try dispatch task to {}, groupId: {}", freeWorker.getMemberId(), freeWorker.getGroupId());
                String result = doDispatch(this.partnerId, freeWorker, itemManager, returnTasks);
                if (Objects.equals(result, FAIL_REASON_TASK_PIN)) {
                    continue;
                } else if (Objects.equals(result, FAIL_REASON_NO_TASK)) {
                    log.info("dispatch task to {} failed, no more task for group: {}", freeWorker.getEmployeeId(), freeWorker.getGroupId());
                }
                return;
            } catch (BusinessException ex1) {
                String errorCode = ex1.getErrorCode();
                if (!Objects.equals(errorCode, ReviewDispatchConst.ERR_CODE_ITEM_CONSUMED)) {
                    return;
                } else {
                    log.info("continue dispatch for err code is: {}", errorCode);
                }
            } catch (Exception ex2) {
                log.error("dispatch task to {} failed", freeWorker.getMemberId(), ex2);
                return;
            }
        }
    }

    private void tryDispatchLocked(ItemTaskWorker freeWorker, GroupItemManager itemManager, Set<ItemTask> returnTasks, Set<String> groupIds, boolean preferCheck) {
        log.info("try dispatch task to {}, member.id: {}, groupId: {}, groups: {}", freeWorker.getName(), freeWorker.getMemberId(), freeWorker.getGroupId(), groupIds);
        List<GroupItemManager> itemManagers = ItemAutoDispatchService.getItemManagers(groupIds);
        if (groupIds.size() > 1 && preferCheck) {
            try {
                ItemTask preferredTask = null;
                GroupItemManager preferredManager = itemManager;
                boolean locked = true;
                for (GroupItemManager manager : itemManagers) {
                    if (!manager.tryLock()) {
                        locked = false;
                        break;
                    } else {
                        ItemTask peek = manager.peek(freeWorker.getUserId());
                        if (peek == null) {
                            continue;
                        }
                        if (preferredTask == null) {
                            preferredTask = peek;
                        } else {
                            // 仅当其他任务比当前任务早10分钟以上时，才考虑优先派其他组的
                            if (Duration.between(peek.getCreateTime().toInstant(), preferredTask.getCreateTime().toInstant()).toMinutes() >= 10) {
                                preferredTask = peek;
                                preferredManager = manager;
                            }
                        }
                    }
                }
                if (preferredManager != itemManager && locked) {
                    log.info("dispatch to worker.name:{}, preferred manager: partner.id: {}, group.id: {}", freeWorker.getName(), preferredManager.getPartnerId(), preferredManager.getGroupId());
                    CrmReviewGroupMemberDao memberDao = SpringUtils.getBean(CrmReviewGroupMemberDao.class);
                    List<Record> freeWorkers = memberDao.findFreeOnlineWorkers(preferredManager.getPartnerId(), Collections.singletonList(freeWorker.getUserId()));
                    if (!freeWorkers.isEmpty()) {
                        ItemAutoDispatchService.getRunner(preferredManager.getPartnerId()).dispatch2FreeWorker(freeWorkers.get(0), preferredManager, false);
                    }
                    return;
                }
            } finally {
                for (GroupItemManager manager : itemManagers) {
                    manager.releaseLock();
                }
            }
        }

        ReentrantLock lock = SimpleLock.getLock(freeWorker.getUserId());
        lock.lock();
        try {
            // check user is still free and online
            CrmReviewGroupMemberDao memberDao = SpringUtils.getBean(CrmReviewGroupMemberDao.class);
            List<Record> freeWorkers = memberDao.findFreeOnlineWorkers(this.partnerId, Collections.singletonList(freeWorker.getUserId()));
            if (!freeWorkers.isEmpty()) {
                tryDispatch(freeWorker, itemManager, returnTasks);
            }
        } finally {
            lock.unlock();
        }
    }

    private static boolean debug() {
        return true;
    }

    private String doDispatch(String partnerId, ItemTaskWorker worker, GroupItemManager itemManager, Set<ItemTask> returnTasks) {
        List<String> excludeIds = returnTasks.stream().map(ItemTask::getItemId).collect(Collectors.toList());
        ItemTask itemTask = itemManager.take(worker.getProvinces(), worker.getCity(), null, excludeIds);
        if (itemTask == null) {
            return FAIL_REASON_NO_TASK;
        }

        log.info("do dispatch ,task.id is: {}, work.id: {}, worker.name: {}", itemTask.getItemId(), worker.getMemberId(), worker.getName());
        if (StringUtils.isNotBlank(itemTask.getPreferUserId())) {
            if (!Objects.equals(itemTask.getPreferUserId(), worker.getUserId())) {
                // 看用户是否在线，如果在线，不能安排这个待办事项。
                CrmReviewGroupMemberDao memberDao = SpringUtils.getBean(CrmReviewGroupMemberDao.class);
                if (memberDao.isMemberOnline(itemTask.getPreferUserId())) {
                    log.info("warning: task pin to : {}, skip dispatch now", itemTask.getPreferUserId());
                    returnTasks.add(itemTask);
                    return FAIL_REASON_TASK_PIN;
                }
            }
        }

        ItemDispatchService dispatchService = SpringUtils.getBean(ItemDispatchService.class);
        dispatchService.dispatchByLuobote(partnerId, itemTask, worker);
        if (debug()) {
            ItemManageDao itemManageDao = SpringUtils.getBean(ItemManageDao.class);
            Record item = itemManageDao.getById(itemTask.getItemId());
            log.info("给 {} 组 员工id:{},name:{} 派单成功，item.id:{},name:{}", ReviewGroupUtil.mapGroupType2Name(itemManager.getGroupType()), worker.getMemberId(), worker.getName(), itemTask.getItemId(), item.getStr("name"));
        } else {
            log.info("给 {} 组 员工id:{},name:{} 派单成功，item.id:{}", ReviewGroupUtil.mapGroupType2Name(itemManager.getGroupType()), worker.getMemberId(), worker.getName(), itemTask.getItemId());
        }
        return null;
    }

}
