package com.skr.mdm.api.template;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.github.pagehelper.PageHelper;
import com.google.common.collect.Lists;
import com.skr.mdm.bean.qo.BaseStrategyQO;
import com.skr.mdm.bean.qo.StrategyUserQO;
import com.skr.mdm.dao.DeviceEquipmentDao;
import com.skr.mdm.dto.LoginAdminDTO;
import com.skr.mdm.enums.IssuedTypeEnum;
import com.skr.mdm.enums.ResultCodeEnum;
import com.skr.mdm.enums.StrategyStateEnum;
import com.skr.mdm.result.ResponseResult;
import com.skr.mdm.util.MdmThreadPoolUtil;
import com.skr.mdm.util.PageUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * 默认策略功能接口实现模板
 *
 * @author nfq
 * @date 2022-07-14 10:31
 */
@Component
@Slf4j
public abstract class AbstractDefaultStrategyService implements DefaultStrategyService {

    @Value("${mdm.big-data.batch-process}")
    private Boolean isBatchProcess;

    @Autowired
    private DeviceEquipmentDao equipmentDao;

    @Override
    public ResponseResult strategyInfo(Integer strategyId) {
        return ResponseResult.success(getStrategyInfo(strategyId));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult issuedStrategy(List<Integer> strategyIdList, BaseStrategyQO baseStrategyQO, LoginAdminDTO loginAdminDTO) {
        if (CollectionUtils.isEmpty(strategyIdList)) {
            return ResponseResult.success();
        }
        if (strategyStateCheck(strategyIdList) > 0) {
            return ResponseResult.failure(ResultCodeEnum.STRATEGY_DISABLE);
        }
        // 更新策略状态
        if (StrategyStateEnum.ISSUED.getState().equals(baseStrategyQO.getState())) {
            updateStrategyIssuedState(strategyIdList, baseStrategyQO);
        }
        // 策略部门全局数据更新
        if (IssuedTypeEnum.DEPT_GLOBAL.getIssuedType().equals(baseStrategyQO.getGlobalType()) &&
                CollectionUtils.isNotEmpty(baseStrategyQO.getCandidateIdList())) {
            updateStrategyDeptGlobal(strategyIdList, baseStrategyQO.getCandidateIdList());
        }
        // 获取设备ID集合
        List<Integer> equipmentIdList = getEquipmentIdList(strategyIdList, baseStrategyQO, loginAdminDTO);
        // 下发日志
        adminLogIssuedStrategy(strategyIdList, baseStrategyQO, loginAdminDTO);
        if (CollectionUtils.isEmpty(equipmentIdList)) {
            return ResponseResult.success();
        }
        // 保存并下发 批量处理
        batchProcess(equipmentIdList, 10000, strategyIdList, loginAdminDTO);
        return ResponseResult.success();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult addStrategy(Object operationObject, LoginAdminDTO loginAdminDTO) {
        BaseStrategyQO baseStrategyQO = (BaseStrategyQO) operationObject;

        long duplicateCheck = checkDuplicateStrategy(operationObject, loginAdminDTO);
        if (duplicateCheck > 0) {
            return ResponseResult.failure(ResultCodeEnum.STRATEGY_NAME_REPEAT);
        }
        Integer strategyId = saveStrategy(operationObject, loginAdminDTO);
        adminLogAddStrategy(loginAdminDTO);
        if (!isBatchProcess && StrategyStateEnum.ISSUED.getState().equals(baseStrategyQO.getState())) {
            return issuedStrategy(ListUtil.toList(strategyId), baseStrategyQO, loginAdminDTO);
        }
        return ResponseResult.success(strategyId);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult updateStrategy(Object operationObject, LoginAdminDTO loginAdminDTO) {
        BaseStrategyQO baseStrategyQO = (BaseStrategyQO) operationObject;

        long duplicateCheck = checkDuplicateStrategy(operationObject, loginAdminDTO);
        if (duplicateCheck > 0) {
            return ResponseResult.failure(ResultCodeEnum.STRATEGY_NAME_REPEAT);
        }
        Integer strategyId = updateStrategyById(operationObject, loginAdminDTO);
        adminLogAddStrategy(loginAdminDTO);
        if (!isBatchProcess && StrategyStateEnum.ISSUED.getState().equals(baseStrategyQO.getState())) {
            return issuedStrategy(ListUtil.toList(strategyId), baseStrategyQO, loginAdminDTO);
        }
        return ResponseResult.success(strategyId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult deleteStrategy(List<Integer> strategyIdList, LoginAdminDTO loginAdminDTO) {
        if (CollectionUtils.isEmpty(strategyIdList)) {
            return ResponseResult.success();
        }
        adminLogDeleteStrategy(strategyIdList, loginAdminDTO);
        issuedStrategyDelete(strategyIdList, loginAdminDTO);
        doDeleteStrategy(strategyIdList);
        return ResponseResult.success();
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult updateStrategyState(Integer strategyId, Integer state, LoginAdminDTO loginAdminDTO) {
        List<Integer> equipmentIdList = getStrategyEquipmentIdList(strategyId, loginAdminDTO);
        // 管理员操作日志
        adminLogUpdateStrategyState(strategyId, state, equipmentIdList, loginAdminDTO);
        if (StrategyStateEnum.ISSUED.getState().equals(state)) {
            List<Integer> deviceIdList = Lists.newArrayListWithCapacity(equipmentIdList.size());
            deviceIdList.addAll(equipmentIdList);
            saveStrategyUser(ListUtil.toList(strategyId), deviceIdList, loginAdminDTO);
        }
        issuedStrategyState(strategyId, state, equipmentIdList, loginAdminDTO);
        // 更新策略状态
        doUpdateStrategyState(strategyId, state, loginAdminDTO);
        return ResponseResult.success();
    }

    @Override
    public ResponseResult listStrategyUser(Object queryObject) {
        StrategyUserQO strategyUserQO = (StrategyUserQO) queryObject;
        PageHelper.startPage(strategyUserQO.getPageNum(), strategyUserQO.getPageSize());
        return ResponseResult.success(PageUtil.getPageResult(getStrategyUserList(strategyUserQO.getStrategyId(),
                strategyUserQO.getSearchType(), strategyUserQO.getSearchValue())));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult deleteStrategyUser(Integer strategyId, List<Integer> equipmentIdList, LoginAdminDTO loginAdminDTO) {
        // 修改策略操作人和操作时间
        doUpdateStrategyState(strategyId, null, loginAdminDTO);
        // 下发
        issuedStrategyUserDelete(strategyId, equipmentIdList, loginAdminDTO);
        // 删除
        doDeleteStrategyUser(strategyId, equipmentIdList);
        // 管理员日志
        adminLogDeleteStrategyUser(strategyId, equipmentIdList, loginAdminDTO);
        return ResponseResult.success();
    }

    /**
     * 获取策略详情
     *
     * @param strategyId 策略ID
     * @return 具体策略详情VO
     */
    protected abstract Object getStrategyInfo(Integer strategyId);

    /**
     * 策略状态检查（禁用状态不能下发）
     *
     * @param strategyIdList 策略ID集合
     * @return 如果大于0，禁止下发
     */
    protected abstract long strategyStateCheck(List<Integer> strategyIdList);

    /**
     * 修改策略下发状态
     *
     * @param strategyIdList 策略ID集合
     * @param baseStrategyQO 策略通用对象
     */
    protected abstract void updateStrategyIssuedState(List<Integer> strategyIdList, BaseStrategyQO baseStrategyQO);

    /**
     * 更新策略部门全局数据
     *
     * @param strategyIdList   策略ID集合
     * @param departmentIdList 全局部门集合
     */
    protected abstract void updateStrategyDeptGlobal(List<Integer> strategyIdList, List<Integer> departmentIdList);

    /**
     * 获取设备ID集合
     *
     * @param strategyIdList 策略ID集合
     * @param baseStrategyQO 策略通用对象
     * @param loginAdminDTO  当前管理员信息
     * @return 设备ID集合
     */
    protected List<Integer> getEquipmentIdList(List<Integer> strategyIdList, BaseStrategyQO baseStrategyQO, LoginAdminDTO loginAdminDTO) {
        if (StrategyStateEnum.SAVE.getState().equals(baseStrategyQO.getState())) {
            return Collections.emptyList();
        }
        if (IssuedTypeEnum.GLOBAL.getIssuedType().equals(baseStrategyQO.getGlobalType())) {
            return equipmentDao.listEquipmentIdByCompanyId(loginAdminDTO.getCompanyId());
        }
        if (IssuedTypeEnum.DEPT_GLOBAL.getIssuedType().equals(baseStrategyQO.getGlobalType())) {
            if (CollectionUtils.isNotEmpty(baseStrategyQO.getCandidateIdList())) {
                return equipmentDao.listEquipmentIdByDepartmentIdList(baseStrategyQO.getCandidateIdList());
            }
            return getEquipmentIdListByStrategyIdListForDeptGlobal(strategyIdList);
        }
        if (IssuedTypeEnum.DEVICE.getIssuedType().equals(baseStrategyQO.getGlobalType())) {
            if (CollectionUtils.isNotEmpty(baseStrategyQO.getCandidateIdList())) {
                return baseStrategyQO.getCandidateIdList();
            }
            return getEquipmentIdListByStrategyIdList(strategyIdList);
        }
        return Collections.emptyList();
    }

    /**
     * 获取策略设备ID集合
     *
     * @param strategyIdList 策略ID集合
     * @return 策略设备ID集合
     */
    protected abstract List<Integer> getEquipmentIdListByStrategyIdList(List<Integer> strategyIdList);

    /**
     * 获取部门全局策略设备ID集合
     *
     * @param strategyIdList 策略ID集合
     * @return 部门全局策略设备ID集合
     */
    protected abstract List<Integer> getEquipmentIdListByStrategyIdListForDeptGlobal(List<Integer> strategyIdList);

    /**
     * 管理员日志（下发策略）
     *
     * @param strategyIdList  策略ID集合
     * @param baseStrategyQO  下发对象
     * @param loginAdminDTO   当前管理员信息
     */
    protected abstract void adminLogIssuedStrategy(List<Integer> strategyIdList, BaseStrategyQO baseStrategyQO, LoginAdminDTO loginAdminDTO);

    /**
     * 保存策略作用人
     *
     * @param strategyIdList  策略ID集合
     * @param equipmentIdList 下发设备ID集合
     * @param loginAdminDTO   当前管理员信息
     */
    protected abstract void saveStrategyUser(List<Integer> strategyIdList, List<Integer> equipmentIdList, LoginAdminDTO loginAdminDTO);

    /**
     * 执行策略下发
     *
     * @param strategyIdList  策略ID集合
     * @param equipmentIdList 下发设备ID集合
     * @param loginAdminDTO   当前管理员信息
     */
    protected abstract void doIssuedStrategy(List<Integer> strategyIdList, List<Integer> equipmentIdList, LoginAdminDTO loginAdminDTO);

    /**
     * 策略名查重
     *
     * @param operationObject 具体策略创建对象
     * @param loginAdminDTO   当前管理员信息
     * @return 策略主键ID
     */
    protected abstract long checkDuplicateStrategy(Object operationObject, LoginAdminDTO loginAdminDTO);

    /**
     * 保存策略（添加）
     *
     * @param operationObject 具体策略创建对象
     * @param loginAdminDTO   当前管理员信息
     * @return 策略主键ID
     */
    protected abstract Integer saveStrategy(Object operationObject, LoginAdminDTO loginAdminDTO);

    /**
     * 管理员日志（添加/编辑策略）
     *
     * @param loginAdminDTO 当前管理员信息
     */
    protected abstract void adminLogAddStrategy(LoginAdminDTO loginAdminDTO);

    /**
     * 编辑策略
     *
     * @param operationObject 具体策略编辑对象
     * @param loginAdminDTO   当前管理员信息
     * @return 策略主键ID
     */
    protected abstract Integer updateStrategyById(Object operationObject, LoginAdminDTO loginAdminDTO);

    /**
     * 策略删除下发
     *
     * @param strategyIdList 策略ID集合
     * @param loginAdminDTO  当前管理员信息
     */
    protected abstract void issuedStrategyDelete(List<Integer> strategyIdList, LoginAdminDTO loginAdminDTO);

    /**
     * 执行策略删除
     *
     * @param strategyIdList 策略ID集合
     */
    protected abstract void doDeleteStrategy(List<Integer> strategyIdList);

    /**
     * 管理员日志（删除策略）
     *
     * @param strategyIdList 策略ID集合
     * @param loginAdminDTO  当前管理员信息
     */
    protected abstract void adminLogDeleteStrategy(List<Integer> strategyIdList, LoginAdminDTO loginAdminDTO);

    /**
     * 修改策略状态
     *
     * @param strategyId    策略ID
     * @param state         策略状态（2：启用 3：禁用）
     * @param loginAdminDTO 当前管理员信息
     */
    protected abstract void doUpdateStrategyState(Integer strategyId, Integer state, LoginAdminDTO loginAdminDTO);

    /**
     * 获取策略作用人
     *
     * @param strategyId    策略ID
     * @param loginAdminDTO 当前管理员信息
     * @return 策略作用人设备ID集合
     */
    protected List<Integer> getStrategyEquipmentIdList(Integer strategyId, LoginAdminDTO loginAdminDTO) {
        BaseStrategyQO baseStrategyQO = createBaseStrategyQO(strategyId);
        return getEquipmentIdList(ListUtil.toList(strategyId), baseStrategyQO, loginAdminDTO);
    }

    /**
     * 创建BaseStrategyQO
     *
     * @param strategyId 策略ID
     * @return BaseStrategyQO
     */
    protected abstract BaseStrategyQO createBaseStrategyQO(Integer strategyId);


    /**
     * 管理员日志（启用、禁用策略）
     *
     * @param strategyId      策略ID
     * @param state           策略状态（2：启用 3：禁用）
     * @param equipmentIdList 策略作用人集合
     * @param loginAdminDTO   当前管理员信息
     */
    protected abstract void adminLogUpdateStrategyState(Integer strategyId, Integer state, List<Integer> equipmentIdList, LoginAdminDTO loginAdminDTO);

    /**
     * 策略状态（启用/禁用)下发
     *
     * @param strategyId      策略ID
     * @param state           策略状态（2：启用 3：禁用）
     * @param equipmentIdList 策略作用人集合
     * @param loginAdminDTO   当前管理员信息
     */
    protected abstract void issuedStrategyState(Integer strategyId, Integer state, List<Integer> equipmentIdList, LoginAdminDTO loginAdminDTO);

    /**
     * 获取策略作用人列表
     *
     * @param strategyId  策略ID
     * @param searchType  检索类型
     * @param searchValue 检索内容
     * @return 策略作用人VO
     */
    protected abstract List<?> getStrategyUserList(Integer strategyId, Integer searchType, String searchValue);

    /**
     * 策略作用人删除下发
     *
     * @param strategyId      策略ID
     * @param equipmentIdList 设备ID集合
     * @param loginAdminDTO   当前管理员信息
     */
    protected abstract void issuedStrategyUserDelete(Integer strategyId, List<Integer> equipmentIdList, LoginAdminDTO loginAdminDTO);

    /**
     * 执行策略作用人删除
     *
     * @param strategyId      策略ID
     * @param equipmentIdList 设备ID集合
     */
    protected abstract void doDeleteStrategyUser(Integer strategyId, List<Integer> equipmentIdList);

    /**
     * 管理员日志（策略作用人删除）
     *
     * @param strategyId      策略ID
     * @param equipmentIdList 设备ID集合
     * @param loginAdminDTO   当前管理员信息
     */
    protected abstract void adminLogDeleteStrategyUser(Integer strategyId, List<Integer> equipmentIdList, LoginAdminDTO loginAdminDTO);

    public void batchProcess(List<Integer> equipmentIdList, Integer batchNum, List<Integer> strategyIdList, LoginAdminDTO loginAdminDTO) {
        if (isBatchProcess && equipmentIdList.size() > batchNum) {
            try {
                List<List<Integer>> partition = Lists.partition(equipmentIdList, batchNum);
                CountDownLatch countDownLatch = new CountDownLatch(partition.size());
                for (List<Integer> saveEquipmentIdList : partition) {
                    MdmThreadPoolUtil.execute(() -> {
                        TimeInterval timer = DateUtil.timer();
                        // 保存
                        saveStrategyUser(strategyIdList, saveEquipmentIdList, loginAdminDTO);
                        // 下发
                        doIssuedStrategy(strategyIdList, saveEquipmentIdList, loginAdminDTO);
                        countDownLatch.countDown();
                        log.info("线程：{}-{}条数据执行完成 耗时：{}毫秒", Thread.currentThread().getName(), saveEquipmentIdList.size(), timer.intervalMs());
                    });
                }
                countDownLatch.await();
            } catch (Exception e) {
                log.error("异步下发异常", e);
            }
        } else {
            // 保存
            saveStrategyUser(strategyIdList, equipmentIdList, loginAdminDTO);
            // 下发
            doIssuedStrategy(strategyIdList, equipmentIdList, loginAdminDTO);
        }
    }
}