package io.wdd.agent.executor.thread;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.wdd.agent.config.utils.AgentCommonThreadPool;
import io.wdd.agent.executor.reply.SimpleStreamSender;
import io.wdd.agent.message.OMessageToServerSender;
import io.wdd.common.beans.rabbitmq.OctopusMessage;
import io.wdd.common.handler.MyRuntimeException;
import io.wdd.common.utils.TimeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;


/**
 * utils to cache store the command execution logs
 * 2023年2月22日  同样需要取出存着的日志
 */
@Service
@Slf4j
public class CommandExecLogCache {

    /**
     * 存储命令执行为空的默认空结果
     */
    private static final ArrayList<String> EmptyCommandLog = new ArrayList<>();
    // 存储命令执行缓存日志
    private static final HashMap<String, ArrayList<String>> CachedCommandLogMap = new HashMap<>();
    /**
     * 存储每一个任务对应的 日志读取BufferedReader
     */
    private static final HashMap<String, BufferedReader> CommandLogBufferedReaderMap = new HashMap<>();


    /**
     * 固定单进程,用于缓存命令执行日志,关闭命令输入管道
     */
    private static ExecutorService LogCacheDaemonThread = null;

    static {

        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setDaemon(true)
                .setNameFormat("exec-log-cache")
                .setPriority(7)
                .build();

        LogCacheDaemonThread = Executors.newSingleThreadExecutor(threadFactory);
    }

    @Resource
    SimpleStreamSender simpleStreamSender;

    @Resource
    OMessageToServerSender toServerSender;

    @Resource
    ObjectMapper objectMapper;

    /**
     * 缓存命令执行日志
     *
     * @param streamKey 根据 命令执行结果Key 定位缓存队列
     * @param process   植物执行队列
     */
    public void cacheLog(String streamKey, Process process) {

        ProcessHandle.Info info = process.info();

        if (ObjectUtils.isEmpty(info)) {
            log.error("process is null ! cache log error !");
            throw new MyRuntimeException();
        }
        log.debug(
                "开始缓存命令执行日志！ process is {}",
                info
        );

        // 这里需要采用异步的方式执行
        // cache the real command logs
        doCacheLog(
                streamKey,
                process
        );

    }


    /**
     * 实际执行命令缓存操作
     *
     * @param streamKey
     */
    private void doCacheLog(String streamKey, Process process) {

        ArrayList<String> commandCachedLog = new ArrayList<>(128);

        String execCommandString = String.format(
                "execution command are => [ %s ]",
                process
                        .info()
                        .commandLine()
                        .get()
        );

        String execTimeString = String.format(
                "execution date time is => [ %s ]",
                TimeUtils.currentTimeString()
        );

        String execMachineString = String.format(
                "execution machine is => [ %s ]",
                streamKey.split("-Execution")[0]
        );


        // add the command
        commandCachedLog.add(execCommandString);
        commandCachedLog.add(execTimeString);
        commandCachedLog.add(execMachineString);
        commandCachedLog.add("--------------- command result are as below --------------------");
        commandCachedLog.add("");

        // 最为重要的一句,将内容放进去
        CachedCommandLogMap.put(
                streamKey,
                commandCachedLog
        );

        log.debug("doCacheLog 开始从process的结果中获取日志缓存");

        // read from input stream and store to the cacheArrayList

        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(
                        process.getInputStream()
                )
        );
        // 缓存这个 日志读取器
        CommandLogBufferedReaderMap.put(
                streamKey,
                bufferedReader
        );

        // 使用统一进程执行缓存和关停动作
        LogCacheDaemonThread.submit(
                () -> {
                    log.debug("开始获取bufferedReader中每一行的内容");
                    // !! 此处会阻塞
                    // 阻塞读取命令执行日志的输出流
                    bufferedReader
                            .lines()
                            .forEach(
                                    commandCachedLog::add
                            );

                    //  对于即时执行完成的任务，需要在这里增加尾巴内容
                    addCommandExecTailInfo(
                            1,
                            streamKey,
                            0
                    );

                    log.debug(
                            "命令代码 [ {} ] 的执行日志内容为  {} ",
                            streamKey,
                            commandCachedLog
                    );
                }
        );
    }

    /**
     * 增加尾巴内容
     *
     * @param process
     * @param streamKey
     */
    private void addCommandExecTailInfo(int timeOut, String streamKey, int exitValue) {

        log.debug("开始添加任务日志的尾部信息！");

        // 添加任务结束的一些信息
        String execTimeCostString = String.format(
                "execution time-cost is => [ %s ]",
                timeOut
        );

        String execResultString = String.format(
                "execution result code is => [ %s ]",
                exitValue
        );

        ArrayList<String> commandExecCachedLog = CachedCommandLogMap.get(streamKey);

        commandExecCachedLog.add("");
        commandExecCachedLog.add("");
        commandExecCachedLog.add("--------------- command result are as above --------------------");
        commandExecCachedLog.add(execTimeCostString);
        commandExecCachedLog.add(execResultString);

        log.debug(
                "添加尾部信息完成， 结果为 => {}",
                commandExecCachedLog
        );
    }

    /**
     * 对于一些没有中止的任务,必须要手动将读取的 InputStream流关闭
     * 否则部分任务的日志无法收集
     */
    public void StopCollectExecLog(String streamKey, Process process, int commandExecWaitTimeout) {

        BufferedReader bufferedReader = CommandLogBufferedReaderMap.get(streamKey);

        log.debug(
                "开始关停任务 [ {} ]对应的日志读取 BufferedReader {}",
                streamKey,
                bufferedReader
        );

        LogCacheDaemonThread.submit(
                () -> {
                    try {
                        // 关闭command的输出流
                        process
                                .getInputStream()
                                .close();

                        bufferedReader.close();

                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } finally {
                        log.debug(
                                "关闭 [{}]的各种输入流读取器成功 !",
                                streamKey
                        );

                        // 任务卡住了到现在，需要强行中止，并且添加部分日志信息
                        if (ObjectUtils.isNotEmpty(process) && process.isAlive()) {

                            addCommandExecTailInfo(
                                    commandExecWaitTimeout,
                                    streamKey,
                                    233
                            );
                        }

                        // 移除
                        CommandLogBufferedReaderMap.remove(streamKey);
                    }
                });
    }

    /**
     * 漂亮的打印出缓存的日志内容
     *
     * @param streamKey
     */
    public void PrintCommandCachedLog(String streamKey) {

        ArrayList<String> cachedCommandLog = CachedCommandLogMap.getOrDefault(
                streamKey,
                EmptyCommandLog
        );

        try {
            String s = objectMapper
                    .writerWithDefaultPrettyPrinter()
                    .writeValueAsString(cachedCommandLog);

            System.out.println(s);

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 日志回传操作
     * 默认发送值Redis中
     * 如果需要持久化，则需要发送至RabbitMQ中
     *
     * @param streamKey
     * @param needResultReplay
     * @param octopusMessage
     */
    public void CollectAndSendExecLog(String streamKey, boolean needResultReplay, OctopusMessage octopusMessage) {

        // 日志操作，如果需要显示回传，需要将日志发送回相应的MQ中
        // 使用异步的方式
        AgentCommonThreadPool.pool.submit(
                () ->
                        collectCommandLogAndRepeat(
                                streamKey,
                                needResultReplay,
                                octopusMessage
                        )
        );
    }

    private void collectCommandLogAndRepeat(String streamKey, boolean needResultReplay, OctopusMessage octopusMessage) {

        // 获取到命令执行的结果， 此操作会清除掉缓存
        ArrayList<String> commandExecLogCachedLog = GetAndCleanExecCacheLog(streamKey);

        /*log.debug(
                "从缓存中获取到的命令执行日志为: {}",
                commandExecLogCachedLog
        );*/

        // 简单的发送到StreamSender
        simpleStreamSender.sendLog(
                streamKey,
                commandExecLogCachedLog
        );

        // 需要 回复 相应的消息
        if (needResultReplay) {
            log.debug("需要准确回复执行命令结果");

            // 构造相应的数据
            octopusMessage.setAc_time(TimeUtils.currentFormatTime());
            octopusMessage.setResult(commandExecLogCachedLog);

            // 发送返回执行完成的 OM结果
            toServerSender.send(octopusMessage);

        }

    }

    /**
     * 获取缓存的 命令执行缓存日志
     *
     * @param streamKey
     * @return
     */
    private ArrayList<String> GetAndCleanExecCacheLog(String streamKey) {

        // 获取
        ArrayList<String> execLogCacheArrayList = CachedCommandLogMap.getOrDefault(
                streamKey,
                EmptyCommandLog
        );

        // 清除Key
        CachedCommandLogMap.remove(streamKey);

        return execLogCacheArrayList;
    }

    /**
     * 调试打印process哥各个 流
     *
     * @param process
     */
    public void debugProcessStreams(Process process) {

        log.debug(
                "开始对一个process进行debug操作,process的信息为 {}, 执行时间为 {} , 执行结果为 {}",
                process
                        .info()
                        .commandLine(),
                process
                        .info()
                        .totalCpuDuration()
                        .get(),
                process.exitValue()
        );

        log.debug("-------------  inputStream -----------");
        new BufferedReader(new InputStreamReader(process.getInputStream()))
                .lines()
                .forEach(
                        System.out::println
                );


        log.debug("-------------  errorStream -----------");
        new BufferedReader(new InputStreamReader(process.getErrorStream()))
                .lines()
                .forEach(
                        System.out::println
                );

    }

    /**
     * 获取到命令执行日志 缓存存储实际的ArrayList
     *
     * @param streamKey
     * @return
     */
    @Deprecated
    public ArrayList<String> getCommandExecLogCacheArrayList(String streamKey) {

        int keyToIndex = this.hashStreamKeyToCachedArrayListIndex(streamKey);

        return CachedCommandLogMap.get(keyToIndex);
    }


    /**
     * 根据返回的ResultKey计算出来，缓存的位置
     *
     * @param streamKey
     * @return
     */
    private int hashStreamKeyToCachedArrayListIndex(String streamKey) {
        int size = CachedCommandLogMap.size();
        return Math.abs(streamKey.hashCode() % size);
    }


    @Deprecated
    private int hashStreamKeyToCachedArrayListIndexWithProblem(String streamKey) {

        return 0;
        /*int size = CachedCommandLog.size();
        int result = Math.abs(streamKey.hashCode() % size);
        boolean hasRehashed = false;

        if (CollectionUtils.isNotEmpty(CachedCommandLog.get(result))) {
            for (int index = result + 1; index < CachedCommandLog.size(); index++) {
                // from the index to the end
                if (CollectionUtils.isEmpty(CachedCommandLog.get(index))) {
                    hasRehashed = true;
                    result = index;
                    break;
                }
            }
            if (!hasRehashed) {
                for (int index = 0; index < result; index++) {
                    // from begin to the index
                    if (CollectionUtils.isEmpty(CachedCommandLog.get(index))) {
                        hasRehashed = true;
                        result = index;
                        break;
                    }
                }
            }

            if (!hasRehashed) {
                // reach here means no empty cache array list
                CachedCommandLog.add(
                        new ArrayList<>(256)
                );
                result = size;
            }
        }

        return result;*/
    }

}
