package net.northking.cctp.se.plan;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.rabbitmq.client.GetResponse;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.se.config.AtuServerConfig;
import net.northking.cctp.se.dto.ScriptEngineInfo;
import net.northking.cctp.se.exec.constant.AtuExecConstant;
import net.northking.cctp.se.lifecycle.EngineRuntime;
import net.northking.cctp.se.plan.bean.AutoTask;
import net.northking.cctp.se.plan.bean.EnginePlanInfo;
import net.northking.cctp.se.plan.servcie.EnginePlanInfoService;
import net.northking.cctp.se.plan.servcie.PlanDeviceService;
import net.northking.cctp.se.thread.ExecutorPool;
import net.northking.cctp.se.util.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.util.*;


/**
 * 定时获取任务执行
 */
@Component
public class AtuPlanTaskSchedule {

    private static final Logger logger = LoggerFactory.getLogger(AtuPlanTaskSchedule.class);
    @VisibleForTesting
    private String TYPE_MOBILE = "2";
    @VisibleForTesting
    private String TYPE_PC = "1";
    @VisibleForTesting
    private String TYPE_INTERFACE  = "3";

    @Autowired
    private EnginePlanInfoService enginePlanInfoService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private AtuServerConfig atuServerConfig;

    @Autowired
    private RabbitAdmin rabbitAdmin;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private PlanDeviceService planDeviceService;

    @Autowired
    private ObjectMapper om;

    /**
     * 定时获取移动端空闲设备检查空闲后占用并领取任务
     */
    @Scheduled(initialDelay = 30 * 1000,fixedDelay = 30 * 1000)
    public void sendTask2MobileDevice(){
        // 判断是否有可用线程
        if (!ExecutorPool.enableMobile){
            logger.error("未开启移动执行线程");
            return;
        }
        logger.debug("移动线程池执行情况: {}", ExecutorPool.getMobilePool().getActiveCount() + "/" +
                ExecutorPool.getMobilePool().getCorePoolSize());
        if (!ExecutorPool.isMobileAvailable()){
            logger.error("移动线程池中可执行线程已占满");
            return;
        }
        List<String> list = new ArrayList<>();
        try {
            list = enginePlanInfoService.queryAllDeviceId(TYPE_MOBILE);
        } catch (Exception e) {
            logger.error("引擎获取执行设备信息失败设备：",e);
        }
        if(list.size()>0){
            //获取引擎注册后的设备id
            EngineRuntime engineRuntime = EngineRuntime.getRuntime();
            ScriptEngineInfo info = engineRuntime.getEngineInfo();
            Map<String,Object> paramMap = new HashMap<>();
            paramMap.put("id",info.getEngineId());
            paramMap.put("deviceList",list);
            paramMap.put("type","1");
            paramMap.put("userBy","engine");
            String url = atuServerConfig.getServerUrl() + atuServerConfig.getTryAcqurieDeviceUrl();
            try {
                ResponseEntity<ResultWrapper> result = restTemplate.postForEntity(url, paramMap, ResultWrapper.class);
                if(HttpStatus.OK.equals(result.getStatusCode())){
                    ResultWrapper deviceResult = result.getBody();
                    if (deviceResult != null && deviceResult.isSuccess()) {
                        List<Map<String,String>> body = (List<Map<String,String>>)deviceResult.getData();
                        if(!CollectionUtils.isEmpty(body)){
                            logger.debug("移动任务请求占用成功移动端设备信息："+JSON.toJSONString(body));
                            for (int i = 0; i < body.size(); i++) {
                                Map<String, String> deviceMap =  body.get(i);
                                String deviceId = deviceMap.get("id");
                                String deviceToken = deviceMap.get("token");
                                //获取设备对应的一条计划信息
                                boolean isUsed = getTaskFromQueue(deviceId, deviceToken,TYPE_MOBILE);
                                if(!isUsed){
                                    //无任务 释放设备
                                    logger.debug("无任务释放移动端设备："+deviceId);
                                    releaseDevice(deviceToken);
                                }
                            }
                        }
                    }

                }
            }catch (Exception e){
                logger.error("请求占用设备失败，原因：",e);
                //释放所有设备
                releaseDevices(e, info.getEngineId(), list);
            }
        }
    }

    /**
     * 引擎领取pc任务
     */
    @Scheduled(initialDelay = 20 * 1000,fixedDelay = 20 * 1000)
    public void sendTask2PcDevice(){
        //获取引擎注册后的设备id
        EngineRuntime engineRuntime = EngineRuntime.getRuntime();
        ScriptEngineInfo info = engineRuntime.getEngineInfo();
        String deviceId = info.getDeviceId();
        String engineId = info.getEngineId();
        List<EnginePlanInfo> batchList = enginePlanInfoService.queryInfoByDeviceId(deviceId,TYPE_PC);
        if(!CollectionUtils.isEmpty(batchList)){
            logger.debug("存在PC类型的委托任务共{}条", batchList.size());
            if(batchList.size()>0){
                Map<String,Object> paramMap = new HashMap<>();
                List<String> list = new ArrayList<>();
                list.add(deviceId);
                paramMap.put("id",engineId);
                paramMap.put("deviceList",list);
                paramMap.put("type","2");
                paramMap.put("userBy","engine");
                //判断设备是否空闲
                String url = atuServerConfig.getServerUrl() + atuServerConfig.getTryAcqurieDeviceUrl();
                try {
                    ResponseEntity<ResultWrapper> resultEntity = restTemplate.postForEntity(url, paramMap, ResultWrapper.class);
                    logger.debug("pc任务请求设备使用结果："+ JSON.toJSONString(resultEntity));
                    if (HttpStatus.OK.equals(resultEntity.getStatusCode())) {
                        ResultWrapper deviceResult = resultEntity.getBody();
                        if (deviceResult!= null && deviceResult.isSuccess()) {
                            List<Map<String, String>> body = (List<Map<String, String>>) deviceResult.getData();
                            if (!CollectionUtils.isEmpty(body)) {
                                Map<String, String> deviceMap = body.get(0);
                                String deviceToken = deviceMap.get("token");
                                logger.debug("设备token：" + deviceToken);
                                logger.debug("当前设备{}占用成功。", deviceId);
                                boolean isUsed = getTaskFromQueue(deviceId, deviceToken, TYPE_PC);
                                if (!isUsed) {
                                    //设备无任务可领
                                    logger.debug("无任务释放PC设备：" + deviceId);
                                    releaseDevice(deviceToken);
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    logger.error("设备请求占用异常：",e);
                    releaseDevices(e, info.getEngineId(), list);
                }
            }
        }
    }

    private void releaseDevices(Exception e, String engineId, List<String> deviceIds) {
        //释放所有设备
        String errorMsg = e.getMessage();
        if (errorMsg.contains("Read timed out")) {
            int count = 0;
            Map<String, Object> requestMap = new HashMap<>();
            requestMap.put("id", engineId);
            requestMap.put("deviceList", deviceIds);
            while (count++ < 50) {
                try {
                    logger.debug("pc任务请求占用设备超时处理====");
                    //请求超时释放请求设备
                    String urlRelease = atuServerConfig.getServerUrl() + atuServerConfig.getReleaseDeviceBatch();
                    ResponseEntity<ResultWrapper> entity = restTemplate.postForEntity(urlRelease, requestMap, ResultWrapper.class);
                    if (HttpStatus.OK == entity.getStatusCode() && entity.getBody() != null && entity.getBody().isSuccess()) {
                        count += 50;
                    }
                    Thread.sleep(2000);
                } catch (Exception innerE) {
                    // todo: 增加发送队列
                    logger.error("超时释放设备失败：", innerE);
                    logger.debug("使用队列方式释放使用的设备：{}", requestMap);
                    try {
                        rabbitTemplate.convertAndSend(AtuExecConstant.ENGINE_RELEASE_ID, new Message(om.writeValueAsBytes(requestMap)));
                        count += 50;
                    } catch (JsonProcessingException jpe) {
                        logger.error("释放设备失败", jpe);
                    }
                }
            }
        }
    }

    /**
     * 引擎领取接口任务
     */
    @Scheduled(initialDelay = 30 * 1000,fixedDelay = 5 * 1000)
    public void sendTask2ApiDevice(){
        //获取引擎注册后的设备id
        EngineRuntime engineRuntime = EngineRuntime.getRuntime();
        ScriptEngineInfo info = engineRuntime.getEngineInfo();
        String deviceId = info.getDeviceId();
        //领取任务
        getTaskFromQueue(deviceId,null,TYPE_INTERFACE);
    }


    /**
     * 每日一时，引擎清理不存在队列的任务
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void clearNoQueueTask(){
        //获取所有任务
        List<EnginePlanInfo> batchList = enginePlanInfoService.queryAllPlanInfo();
        if(!CollectionUtils.isEmpty(batchList)){
            //清楚队列不存在的任务
            logger.info("清除队列不存在的本地任务-----开始");
            for (EnginePlanInfo info : batchList) {
                String queueName = info.getQueueName();
                Properties properties = rabbitAdmin.getQueueProperties(queueName);
                if(null == properties){
                    logger.debug("队列["+queueName+"]不存在。。。");
                    //队列不存在 删除该类型批次信息
                    enginePlanInfoService.deletePlanInfoById(info.getId());
                }
            }
            logger.info("清除队列不存在的本地任务-----结束");
        }

    }


    /**
     * 释放设备
     */
    private void releaseDevice(String deviceToken){
        String releaseUrl = atuServerConfig.getServerUrl() + atuServerConfig.getDeviceReleasePath()+deviceToken;
        try {
            ResponseEntity<ResultWrapper> resultEntity = restTemplate.getForEntity(releaseUrl, ResultWrapper.class);
            if (HttpStatus.OK.equals(resultEntity.getStatusCode())) {
                ResultWrapper bodyResult = resultEntity.getBody();
                if (bodyResult.isSuccess()) {
                    if ((boolean) bodyResult.getData()) {
                        logger.debug("设备退出成功。。。。。");
                    }
                }else{
                    logger.warn("设备释放异常："+JSON.toJSONString(bodyResult));
                }
            }else{
                logger.warn("设备释放异常："+JSON.toJSONString(resultEntity));
            }
        } catch (Exception e1) {
            //设备释放异常重新处理
            logger.error("设备退出异常：",e1);
            try {
                String queueName = AtuExecConstant.ENGINE_RELEASE;
                Properties properties = rabbitAdmin.getQueueProperties(queueName);
                if(null == properties){
                    //声明设备释放队列
                    org.springframework.amqp.core.Queue queue = new org.springframework.amqp.core.Queue(queueName);
                    rabbitAdmin.declareQueue();
                }
                String[] tokens = {deviceToken};
                rabbitTemplate.convertAndSend(queueName,new Message(JSON.toJSONString(Arrays.asList(tokens)).getBytes()));
            } catch (AmqpException e) {
                logger.error("设备退出重试异常：",e);
            }
        }
    }


    /**
     * 根据设备id查询一条队列信息从中获取任务
     */
    private boolean getTaskFromQueue(String deviceId,String deviceToken,String type){
        boolean flag = false;//设备是否继续占用标识
        List<EnginePlanInfo> batchList = enginePlanInfoService.queryInfoByDeviceId(deviceId,type);
        if(!CollectionUtils.isEmpty(batchList)){
            for (int i = 0; i < batchList.size(); i++) {
                EnginePlanInfo planInfo =  batchList.get(i);
                String queueName = planInfo.getQueueName();
                Properties properties = rabbitAdmin.getQueueProperties(queueName);
                if(null == properties){
                    logger.debug("队列["+queueName+"]不存在。。。");
                    //队列不存在 删除该类型批次信息
                    enginePlanInfoService.removeEnginePlanByType(planInfo.getBatchId(),type);
                    planDeviceService.removeBatchInfo(planInfo.getBatchId());
                }else{
                    String browserType = "0";
                    if(TYPE_INTERFACE.equalsIgnoreCase(type)||TYPE_MOBILE.equalsIgnoreCase(type)){
                        flag = planDeviceService.isEnableAcceptTask(type);
                    }else{
                        flag = true;
                        browserType = planInfo.getBrowserType();
                    }
                    if(flag){
                        try {
                            logger.debug("开始从队列{" + queueName + "}中获取一条任务");
                            String finalBrowserType = browserType;
                            Boolean execute = rabbitTemplate.execute(channel -> {
                                GetResponse response = channel.basicGet(queueName, true);
                                if (response != null) {
                                    byte[] body = response.getBody();
                                    String msg = new String(body);
                                    logger.debug("计划任务信息：{}", msg);
                                    try {
                                        AutoTask task = JsonUtils.fromJsonString(msg, AutoTask.class);
                                        task.setDeviceId(deviceId);
                                        task.setTaskType(type);
                                        task.setBrowserType(finalBrowserType);
                                        task.setAppDownloadUrl(atuServerConfig.getServerUrl() + atuServerConfig.getAppDownloadUrl());
                                        logger.debug("任务信息：" + JSON.toJSONString(task));
                                        planDeviceService.sendTask2Exec(task, deviceToken);
                                        return true;
                                    } catch (Exception e) {
                                        logger.debug("消息处理异常，回滚至队列中，消息为：{}", msg);
                                        rabbitTemplate.convertAndSend(queueName, msg);
                                        throw new ExecuteException("获取队列任务并推送执行异常, " + e.getMessage());
                                    }
                                }
                                return false;
                            });
                            flag = execute == null ? false : execute;
                            if (flag) {
                                break;
                            }
                        }catch (Exception e){
                            logger.error("任务领取失败：",e);
                            flag = false;
                        }
                    }
                }
            }
        }
        return flag;
    }
}
