package com.bocloud.ims.service.internal.worker;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bocloud.cmp.model.installation.HostResult;
import com.bocloud.common.enums.BoCloudExchange;
import com.bocloud.common.enums.BoCloudMethod;
import com.bocloud.common.enums.BoCloudService;
import com.bocloud.common.enums.PostDataFormat;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.utils.MapTools;
import com.bocloud.service.http.core.ServiceFactory;
import com.bocloud.service.http.model.RemoteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * 请求worker
 * <p>
 * 进行nim的系统安装等操作
 *
 * @author 007
 */
@Service("requestWorkerService")
public class RequestWorkerService {

	private static final String QUEUE = "queue";
	private static final String TARGETS = "targets";
	private static final Map<String, Object> headers = MapTools.simpleMap("Content-Type", "application/json");

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

	@Autowired
	private ServiceFactory serviceFactory;
	@Autowired
	private AmqpAdmin amqpAdmin;
	@Autowired
	private AmqpTemplate amqpTemplate;
	@Autowired
	private ThreadPoolTaskExecutor taskExecutor;

	/**
	 * 调用worker
	 *
	 * @param param    发送给worker的参数
	 * @param callback 回调的接口
	 * @param context  用于在回调前后传递上下文
	 * @return
	 */
	public BocloudResult invoke(Map<String, Object> param, WorkerCallback callback, Map<String, Object> context) {
		RemoteService remoteService = serviceFactory.build(BoCloudService.WORKER, "/api/worker/v1/job/submit",
				BoCloudMethod.OPERATE, headers, param);
		Future<BocloudResult> future = taskExecutor.submit(() -> {
			BocloudResult result;
			if (!remoteService.isExist()) {
				logger.error("远程Worker不存在!");
				@SuppressWarnings("unchecked")
				List<Map<String, Object>> failedTargets = (List<Map<String, Object>>) param.get(TARGETS);
				List<WorkerHostResult> targetResults = new ArrayList<>();
				for (Map<String, Object> failedTarget : failedTargets) {
					targetResults
							.add(new WorkerHostResult(false, (String) failedTarget.get("host"), null, "远程Worker服务不存在"));
				}
				return new BocloudResult(false, targetResults, "远程Worker服务不存在！");
			}
			// 临时队列
			String resultQueue = UUID.randomUUID().toString();
			amqpAdmin.declareQueue(new Queue(resultQueue, false, false, true));
			amqpAdmin.declareBinding(new Binding(resultQueue, Binding.DestinationType.QUEUE,
					BoCloudExchange.Default.DIRECT.getExchange(), resultQueue, null));
			Map<String, Object> paramMap = new HashMap<>();
			paramMap.put(QUEUE, resultQueue);
			JSONObject oldParams = null;
			if (null == remoteService.getParam().getParams()) {
				oldParams = new JSONObject();
			} else {
				oldParams = JSONObject.parseObject(JSONObject.toJSONString(remoteService.getParam().getParams()));
			}
			if (oldParams.keySet().size() > 0) {
				paramMap.putAll(oldParams);
			}
			remoteService.getParam().setParams(paramMap);

			remoteService.setDataFormat(PostDataFormat.RAW);
			// 调用worker服务
			BocloudResult bocloudResult = remoteService.invoke();
			if (bocloudResult.isSuccess()) {
				// 这里说明请求成功发送到worker上，接下来可以等待结果消息了
				try {
					Message message = amqpTemplate.receive(resultQueue, 1000L * 60 * 30);
					if (null != message) {
						String content = new String(message.getBody());
						logger.info("Received Job result!,content {}", content);
						result = JSONObject.parseObject(content, BocloudResult.class);
					} else {
						logger.error("获取任务执行结果消息超时！");
						result = buildErrorResult("获取任务结果消息超时！", remoteService);
					}
				} catch (Exception e) {
					logger.error("等待worker返回任务结果消息异常:", e);
					result = buildErrorResult(e.getMessage(), remoteService);
				} finally {
					amqpAdmin.deleteQueue(resultQueue);
				}
			} else {
				logger.error("请求发送到worker失败：{}", bocloudResult.getMessage());
				result = buildErrorResult(bocloudResult.getMessage(), remoteService);
			}
			return result;
		});

		// 获取处理结果
		taskExecutor.execute(() -> {
			// 处理任务执行的结果
			while (true) {
				try {
					// 查看每个worker执行结果
					BocloudResult result = future.get();
					handleResult(callback, context, result);
				} catch (InterruptedException e) {
					logger.error("worker任务执行任务异常", e);
					Thread.currentThread().interrupt();
				} catch (ExecutionException e) {
					Throwable throwable = Optional.ofNullable(e.getCause()).orElse(e);
					logger.error("worker任务执行任务异常", throwable);
					handleResult(callback, context, new BocloudResult(false, throwable.getMessage()));
				}
				break;
			}
		});
		return new BocloudResult(true, "任务提交成功");
	}

	/**
	 * 处理请求结果
	 *
	 * @param callback     回调的接口
	 * @param context      用于在回调前后传递上下文
	 * @param workerResult worker的返回
	 */
	private void handleResult(WorkerCallback callback, Map<String, Object> context, BocloudResult workerResult) {
		if (callback != null) {
			callback.notify(context, workerResult);
		}
	}

	private BocloudResult buildErrorResult(String message, RemoteService remoteService) {
		Map remoteServiceParams = (Map) remoteService.getParam().getParams();
		String targets = JSON.toJSONString(remoteServiceParams.get(TARGETS));
		List<HostResult> hostResults = new ArrayList<>();
		List<WorkerHost> workerHosts = JSONObject.parseArray(targets, WorkerHost.class);

		Iterator iterator = workerHosts.iterator();
		while (iterator.hasNext()) {
			WorkerHost target = (WorkerHost) iterator.next();
			hostResults.add(new HostResult(false, message, target.getHost(), target.getRawName(), target.getPort(),
					target.getUser()));
		}
		return new BocloudResult(false, hostResults, message);
	}
}
