package io.wdd.agent.executor;

import com.google.common.io.ByteStreams;
import io.wdd.agent.config.utils.AgentCommonThreadPool;
import io.wdd.agent.executor.reply.StreamSender;
import io.wdd.agent.executor.thread.CommandExecLogCache;
import io.wdd.common.beans.executor.ExecutionMessage;
import io.wdd.common.beans.rabbitmq.OctopusMessage;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;


@Configuration
@Slf4j
public class CommandExecutor {

    @Resource
    StreamSender streamSender;

    @Resource
    CommandExecLogCache commandExecLogCache;

    /**
     * 一个命令执行的最长等待时间
     */
    @Value("${octopus.agent.executor.processMaxTimeOut}")
    Integer processMaxWaitSeconds;

    /**
     * 持久化命令执行的最长等待时间
     * 8小时 8*60*60 =
     */
    int durationTaskMaxWaitSeconds = 8 * 60 * 60;

    /**
     * handle command from octopus server
     *
     * @param executionMessage get from EXECUTOR_HANDLER
     */
    public void execute(OctopusMessage octopusMessage, ExecutionMessage executionMessage) {

        // 防止阻塞消息队列中的其他信息，需要使用异步执行
        AgentCommonThreadPool.pool.submit(
                () -> this.execute(
                        executionMessage.getResultKey(),
                        executionMessage.getSingleLineCommand(),
                        executionMessage.isNeedResultReplay(),
                        executionMessage.isDurationTask(),
                        octopusMessage
                )
        );

    }

    public int execute(String streamKey, List<String> command, boolean needResultReplay, boolean durationTask, OctopusMessage octopusMessage) {

        ProcessBuilder processBuilder = new ProcessBuilder(command);

        return this.processExecute(
                streamKey,
                processBuilder,
                needResultReplay,
                durationTask,
                octopusMessage
        );
    }

    /**
     * 执行最底层命令行操作的代码
     */
    private int processExecute(String streamKey, ProcessBuilder processBuilder, boolean needResultReplay, boolean durationTask, OctopusMessage octopusMessage) {

        // 重定向，错误日志到标准输出中
        processBuilder.redirectErrorStream(true);
        // processBuilder.inheritIO();
        processBuilder.directory(new File(System.getProperty("user.home")));
        int processResult = 233;

        try {
            // 开始执行命令之前，需要进行打印
            log.debug(
                    "current shell command {}",
                    processBuilder.command()
            );

            // 开始执行命令操作
            CountDownLatch countDownLatch = new CountDownLatch(1);

            // 开始执行命令操作
            Process process = processBuilder.start();

            // 守护进程 对每一条任务的超时时间进行限制，避免卡死
            int commandExecWaitTimeout;
            if (durationTask) {
                // 持久化任务的超时等待时间会非常长，但还是采用相同的机制
                log.info(
                        "当前命令为持久化任务，将在 {} 秒之后返回日志",
                        durationTaskMaxWaitSeconds
                );
                commandExecWaitTimeout = durationTaskMaxWaitSeconds;
            } else {
                commandExecWaitTimeout = processMaxWaitSeconds;
            }

            // 2023年2月23日  同时执行log日志部分移动至此部分处理
            AgentCommonThreadPool.pool.submit(
                    () -> DaemonCommandProcessAndCollectLog(
                            process,
                            commandExecWaitTimeout,
                            countDownLatch,
                            needResultReplay,
                            streamKey,
                            octopusMessage
                    ));

            // 等待1秒钟, 使得有时间进行后端任务的完成
            TimeUnit.SECONDS.sleep(1);

            // get the command result must also be a timeout smaller than the process
            // 此处会把主线程卡死, forever终结
            processResult = process.waitFor();

            // 命令执行完成， countDownLatch计数
            countDownLatch.countDown();

            log.debug(
                    "current shell command {} result is {}",
                    processBuilder.command(),
                    processResult
            );

        } catch (IOException | InterruptedException e) {
            log.error(
                    "Shell command error ! {} + {}",
                    e.getCause(),
                    e.getMessage()
            );
        }

        return processResult;
    }

    private void DaemonCommandProcessAndCollectLog(Process process, int commandExecWaitTimeout, CountDownLatch countDownLatch, boolean needResultReplay, String streamKey, OctopusMessage octopusMessage) {

        // 缓存 命令处理日志
        // 如果是特别简单的命令,必须要放在此处才可以,否则会导致 无法收集
        commandExecLogCache.cacheLog(
                streamKey,
                process
        );

        boolean commandExecComplete = false;

        try {
            log.debug(
                    "[ {} ] 命令执行守护进程开始等待 {} 秒",
                    process
                            .info()
                            .commandLine()
                            .get(),
                    commandExecWaitTimeout
            );

            // 使用 countDownLatch 进行超时等待
            commandExecComplete = countDownLatch.await(
                    commandExecWaitTimeout,
                    TimeUnit.SECONDS
            );

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {

            // 任务提前执行结束，或者超过了最长等待时间
            // 判断命令是否正确处理完成
            if (!commandExecComplete) {
                log.warn(
                        "任务 [ {} ]执行超过了最长等待时间 {} 秒, destroy the command process!",
                        process
                                .info()
                                .commandLine()
                                .get(),
                        commandExecWaitTimeout
                );

                // 命令没有正确的处理完成,需要手动关停程序
                // 只有当该进程还存活，执行关闭操作, 没有返回结果的命令如 ping baidu.com 只有在线程关闭之后才可以有结果s
                if (process.isAlive()) {
                    // shutdown the process
                    log.debug(
                            "开始销毁命令 [ {} ] 执行进程!",
                            process
                                    .info()
                    );
                }
            }

            // 打印一下 debug
            //commandExecLogCache.PrintCommandCachedLog(streamKey);

            // 关停任务执行的缓存日志收集 BufferedReader 否则无法终止
            commandExecLogCache.StopCollectExecLog(
                    streamKey,
                    process,
                    commandExecWaitTimeout
            );

            // 执行到这里,说明整个任务流程结束(超时结束)
            log.debug(
                    "命令 [ {} ] [ {} ]  执行全流程结束! 开始释放所有资源",
                    streamKey,
                    process.info()
            );

            // 释放所有的资源
            try {
                process
                        .getInputStream()
                        .close();
                process
                        .getOutputStream()
                        .close();
                process
                        .getErrorStream()
                        .close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            if (process.isAlive()) {
                // shutdown the process
                process.destroyForcibly();
            }


            // 异步执行日志的发送工作
            commandExecLogCache.CollectAndSendExecLog(
                    streamKey,
                    needResultReplay,
                    octopusMessage
            );
        }

    }



    @Deprecated
    private ByteBuffer cvToByteBuffer(InputStream inputStream) throws IOException {

        byte[] toByteArray = ByteStreams.toByteArray(inputStream);

        ByteBuffer bufferByte = ByteBuffer.wrap(toByteArray);

        return bufferByte;
    }

    private String cvToString(InputStream inputStream) throws IOException {

        String s = String.valueOf(ByteStreams.toByteArray(inputStream));

        System.out.println("s = " + s);

        return s;

    }


    @SneakyThrows
    @Deprecated
    public void clearCommandCache(String streamKey) {

        // wait
        TimeUnit.SECONDS.sleep(1);

        // clear the log Cache Thread scope
        commandExecLogCache
                .getCommandExecLogCacheArrayList(streamKey)
                .clear();

        // clear the stream sender
        streamSender.clearLocalCache(streamKey);

    }
}
