package com.xinxincaravan.caravan.vehicle.biz;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.wxiaoqi.security.common.biz.BaseBiz;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.xinxincaravan.caravan.vehicle.common.RestResponse;
import com.xinxincaravan.caravan.vehicle.constant.RedisKey;
import com.xinxincaravan.caravan.vehicle.constant.VehicleMsgStatus;
import com.xinxincaravan.caravan.vehicle.constant.VehicleMsgType;
import com.xinxincaravan.caravan.vehicle.entity.Vehicle;
import com.xinxincaravan.caravan.vehicle.entity.VehicleWarningMsg;
import com.xinxincaravan.caravan.vehicle.entity.VehicleWarningRule;
import com.xinxincaravan.caravan.vehicle.mapper.VehicleMapper;
import com.xinxincaravan.caravan.vehicle.mapper.VehicleWarningMsgMapper;
import com.xinxincaravan.caravan.vehicle.mapper.VehicleWarningRuleMapper;
import com.xinxincaravan.caravan.vehicle.util.JSUtil;
import com.xinxincaravan.caravan.vehicle.vo.AddVehicleWarningMsgVo;
import com.xinxincaravan.caravan.vehicle.vo.PageDataVo;
import com.xinxincaravan.caravan.vehicle.vo.QueryVehicleWarningMsgVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.RowBounds;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class VehicleWarningMsgBiz extends BaseBiz<VehicleWarningMsgMapper, VehicleWarningMsg> {


    private static final Integer CHECK_BATCH_SIZE_VEHICLE = 100;//每批次检查多少辆车

    private static final String PARAM_NAME_4_JS_WARNEDIDANDMSG = "idAndMsg";
    private static final String PARAM_NAME_4_JS_LAST_DAY_MILEAGE = "lastDayMileage";
    private static final String PARAM_NAME_4_JS_NOW_STR = "nowStr";
    private static final String PARAM_NAME_4_JS_VEHICLE = "vehicle";
    private static final String PARAM_NAME_4_JS_CUR_RULE_ID = "curRuleId";
    private static final String VEHICLE_CHECK_RULE_FUNCTION_PREFIX = "checkIfViolateRule";
    private static final String VEHICLE_MSG_KEY_FUNCTION_PREFIX = "getMsgKey";
    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");


    @Autowired
    private VehicleWarningRuleMapper vehicleWarningRuleMapper;

    @Autowired
    private VehicleMapper vehicleMapper;

    @Autowired
    private RedisTemplate customRedisTemplate;

    /**
     * 增加自定义预警消息
     * @param addVehicleWarningMsgVo
     * @return
     * @throws Exception
     */
    public RestResponse add(AddVehicleWarningMsgVo addVehicleWarningMsgVo) throws Exception{
        VehicleWarningMsg vehicleWarningMsg = new VehicleWarningMsg();
        BeanUtils.copyProperties(vehicleWarningMsg,addVehicleWarningMsgVo);
        //新增的都是自定义类型的消息
        vehicleWarningMsg.setType(VehicleMsgType.CUSTOM.getCode());
        //新增消息默认未处理类型
        vehicleWarningMsg.setStatus(VehicleMsgStatus.NOT_PROCESSED.getCode());
        mapper.insertSelective(vehicleWarningMsg);
        return RestResponse.suc();
    }

    /**
     * 按页查询
     * @param queryVehicleWarningMsgVo
     * @return
     */
    public PageDataVo<VehicleWarningMsg> getByPage(QueryVehicleWarningMsgVo queryVehicleWarningMsgVo){
        PageHelper.startPage(queryVehicleWarningMsgVo.getPage(),queryVehicleWarningMsgVo.getLimit());
        List<VehicleWarningMsg> vehicleWarningMsgs = mapper.getByPages(queryVehicleWarningMsgVo);
        PageInfo<VehicleWarningMsg> vehicleWarningMsgPageInfo = new PageInfo<>(vehicleWarningMsgs);
        return PageDataVo.pageInfo(vehicleWarningMsgPageInfo);
    }

    /**
     * 处理预警消息（把状态置为已处理）
     * @param id
     * @return
     */
    public RestResponse deal(Integer id){
        VehicleWarningMsg params = new VehicleWarningMsg();
        params.setId(id);
        params.setStatus(VehicleMsgStatus.PROCESSED.getCode());
        Integer effected = mapper.updateByPrimaryKeySelective(params);
        return RestResponse.suc();
    }


    /**
     * 检查车辆是否需要插入相关预警信息
     */
    @Scheduled(cron = "0 0 0 * * ?")//每日0点触发
    public void checkIfNeedWarn(){
        //获取redis乐观锁，失败则不执行，确保只有一个线程执行
        Boolean hasSuc = customRedisTemplate.opsForValue().setIfAbsent(RedisKey.LOCK_VEHICLE_WARNING_MSG,String.valueOf(DateTime.now().getMillis()));
        if(hasSuc){//设置23小时后过期
            customRedisTemplate.expire(RedisKey.LOCK_VEHICLE_WARNING_MSG,23, TimeUnit.HOURS);
        }else{
            log.info("[预警信息检查]乐观锁获取失败，该线程不执行任务。");
            return;
        }
        log.info("[预警信息检查]任务开始。");
        //获取所有检查规则
        List<VehicleWarningRule> vehicleWarningRules = vehicleWarningRuleMapper.selectAll();
        if(CollectionUtils.isEmpty(vehicleWarningRules)){
            log.info("[预警信息检查]当前不存在任何预警规则，不执行预警信息检查");
            return;
        }
        //按页获取车辆
        Integer curPage = 1;
        List<Vehicle> vehicles = null;
        do{
            RowBounds rowBounds = new RowBounds((curPage - 1)*CHECK_BATCH_SIZE_VEHICLE,CHECK_BATCH_SIZE_VEHICLE);
            curPage++;
            vehicles = vehicleMapper.selectByRowBounds(new Vehicle(),rowBounds);
            if(CollectionUtils.isEmpty(vehicles)){
                continue;
            }
            for(Vehicle vehicle : vehicles){        //逐个车辆执行检查
                checkIfNeedWarnEachVehicle(vehicle,vehicleWarningRules);
            }

        }while(CollectionUtils.isEmpty(vehicles));
    }

    /**
     * 对某辆车执行规则检查，查出规则判断函数中支持的参数，若需预警，则执行相关js表达式生成消息内容
     * @param vehicle
     * @param vehicleWarningRules
     */
    private void checkIfNeedWarnEachVehicle(Vehicle vehicle, List<VehicleWarningRule> vehicleWarningRules){

        //查出当前车辆曾经触发的预警规则列表
        List<VehicleWarningMsg> vehicleWarningMsgs = mapper.getMsgByVehicle(vehicle.getId());
        //转化相关list为map方便使用
        Map<Integer,VehicleWarningMsg> idAndMsg = Maps.newHashMap();
        //提取存在的msgKey
        Set<String> existsKey = new HashSet<>();
        if(!CollectionUtils.isEmpty(vehicleWarningRules)){
            for(VehicleWarningMsg vehicleWarningMsg: vehicleWarningMsgs){
                idAndMsg.put(vehicleWarningMsg.getId(),vehicleWarningMsg);
                if(StringUtils.isNotBlank(vehicleWarningMsg.getMsgKey())){
                    existsKey.add(vehicleWarningMsg.getMsgKey());
                }
            }
        }
        Map<String,Object> params = Maps.newHashMap();
        //填充对应参数
        params.put(PARAM_NAME_4_JS_WARNEDIDANDMSG,idAndMsg);
        params.put(PARAM_NAME_4_JS_NOW_STR,DateTime.now().toString(DATE_TIME_FORMATTER));
        params.put(PARAM_NAME_4_JS_VEHICLE,vehicle);

        //查出并写入车辆最近一次更新的里程数
        Integer lastDayMileage = null;
        if(vehicle.getMileageLastUpdate() != null) {
            String mileageLastUpdateStr = String.valueOf(vehicle.getMileageLastUpdate());
            Object lastDayMileageStrObj = customRedisTemplate.opsForValue().getAndSet(RedisKey.MILEAGE_LAST_DAY_PREFIX + vehicle.getId(),
                    mileageLastUpdateStr);
            String lastDayMileageStr = lastDayMileageStrObj == null ? null : String.valueOf(lastDayMileageStrObj);
            if (StringUtils.isNotBlank(lastDayMileageStr)) {
                lastDayMileage = Integer.parseInt(lastDayMileageStr);
                params.put(PARAM_NAME_4_JS_LAST_DAY_MILEAGE,lastDayMileage);        //填充对应参数
            }
        }
        for(VehicleWarningRule vehicleWarningRule:vehicleWarningRules){
            //传入参数执行对应js函数
            Object jsRet = JSUtil.evalJsFunction(VEHICLE_CHECK_RULE_FUNCTION_PREFIX + vehicleWarningRule.getId(),
                    vehicleWarningRule.getJsFunctionCheck(),params);
            if(!(jsRet instanceof Boolean) || Boolean.FALSE.equals(jsRet)){
                continue;
            }
            params.put(PARAM_NAME_4_JS_CUR_RULE_ID,vehicleWarningRule.getId());
            VehicleWarningMsg vehicleWarningMsg = new VehicleWarningMsg();
            if(StringUtils.isNotBlank(vehicleWarningRule.getJsFunctionMsgKey())){
                //执行相应获取消息唯一键的js函数，用于去重，不存在则无需执行去重检查
                String msgKey = String.valueOf(JSUtil.evalJsFunction(VEHICLE_MSG_KEY_FUNCTION_PREFIX + vehicleWarningRule.getId(),
                        vehicleWarningRule.getJsFunctionMsgKey(),params));
                if(existsKey.contains(msgKey)){//注意此处若没有乐观锁保证该任务单线程执行，有并发问题
                    continue;//去除重复key消息
                }
                vehicleWarningMsg.setMsgKey(msgKey);
            }
            //若触发预警，执行js表达式获取相关msg
            vehicleWarningMsg.setStatus(VehicleMsgStatus.NOT_PROCESSED.getCode());
            vehicleWarningMsg.setType(vehicleWarningRule.getMsgType());
            vehicleWarningMsg.setRuleId(vehicleWarningRule.getId());
            vehicleWarningMsg.setVehicleId(vehicle.getId());
            vehicleWarningMsg.setMsg(String.valueOf(
                    JSUtil.eval(vehicleWarningRule.getJsExpressionMsg(),params)
            ));
            mapper.insertSelective(vehicleWarningMsg);
        }
    }


}
