package cn.reghao.autodop.common.docker;

import cn.reghao.autodop.common.dagent.app.log.LogLevel;
import cn.reghao.autodop.common.docker.api.ContainerOps;
import cn.reghao.autodop.common.docker.api.DockerApi;
import cn.reghao.autodop.common.docker.api.ImageOps;
import cn.reghao.autodop.common.docker.exception.DockerException;
import cn.reghao.autodop.common.docker.pojo.*;
import cn.reghao.autodop.common.docker.pojo.result.Container;
import cn.reghao.autodop.common.docker.pojo.result.ContainerInfo;
import cn.reghao.autodop.common.docker.unixdomain.DockerAuth;
import cn.reghao.autodop.common.docker.unixdomain.DockerHeader;
import cn.reghao.autodop.common.docker.unixdomain.UnixSocketClient;
import cn.reghao.autodop.common.docker.unixdomain.HttpClient;
import cn.reghao.autodop.common.util.ExceptionUtil;
import cn.reghao.jdkutil.serializer.JsonArrayDeserializer;
import cn.reghao.jdkutil.serializer.JsonConverter;
import cn.reghao.jdkutil.security.Base64Util;
import cn.reghao.autodop.common.util.compression.TarFiles;
import cn.reghao.jdkutil.security.Md5Util;
import cn.reghao.jdkutil.text.TextFile;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.netty.handler.codec.http.FullHttpResponse;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Docker 客户端
 *
 * @author reghao
 * @date 2020-01-13 14:16:38
 */
@Slf4j
public class Docker implements ImageOps, ContainerOps, AutoCloseable {
    private HttpClient client = new HttpClient(new UnixSocketClient());
    private TextFile textFile = new TextFile();

    @Override
    public void close() {
        client.close();
    }

    /**
     * 检测 Docker 的响应
     *
     * @param
     * @return
     * @date 2021-02-08 下午2:08
     */
    private String checkResponse(FullHttpResponse response) throws DockerException {
        if (response != null) {
            int statusCode = response.status().code();
            String result = response.content().toString(StandardCharsets.UTF_8);
            if (statusCode >= 400) {
                throw new DockerException(result);
            }

            String[] lines = result.split(System.lineSeparator());
            for (String line : lines) {
                if (line.contains("error") || line.contains("message")) {
                    throw new DockerException("Docker 请求错误 -> " + line);
                }
            }
            return result;
        } else {
            throw new DockerException("Docker 响应是 NULL");
        }
    }

    /**
     * 根据容器名字找出容器 ID
     *
     * @param
     * @return
     * @date 2021-02-25 上午10:31
     */
    public String getIdByName(String name) throws DockerException {
        String wrappedName = "/" + name;
        List<Container> containers = ps(true);
        for (Container container : containers) {
            Set<String> names = new HashSet<>(container.getNames());
            if (names.contains(wrappedName)) {
                return container.getId();
            }
        }
        return null;
    }

    /* image 操作 */
    @Override
    public void build(String repoTag, String compileHome, String dockerfile) throws Exception {
        String uri = DockerApi.buildPost + "?t=" + repoTag;
        if (dockerfile != null) {
            String dockerfilePath = compileHome + "/Dockerfile1";
            textFile.write(new File(dockerfilePath), dockerfile);
            // dockerfile 参数指定的是 Dockerfile 文件在 tar 文件内的相对路径和名字
            uri += "&dockerfile=Dockerfile1";
        }

        // Dockerfile 要包含在 tar 文件中
        // TODO /tmp 目录内文件的删除策略
        String dstPath = "/tmp/" + Md5Util.md5(compileHome) + ".tar";
        TarFiles.tar(compileHome, dstPath);
        File tarFile = new File(dstPath);

        List<DockerHeader> headers = new ArrayList<>();
        headers.add(new DockerHeader("Content-Type", "application/tar"));
        headers.add(new DockerHeader("Expect", "100-continue"));

        FullHttpResponse response = client.postFile(uri, headers, tarFile);
        tarFile.delete();
        checkResponse(response);
    }

    @Override
    public void push(String image) throws DockerException {
        String uri = DockerApi.pushPost.replace("{}", image);

        DockerAuth auth = new DockerAuth();
        auth.setUsername("reghao");
        auth.setPassword("12345678");
        auth.setEmail("reghaodev@gmail.com");
        auth.setServeraddress("localhost");
        String encodeAuth = Base64Util.encode(JsonConverter.objectToJson(auth));
        List<DockerHeader> headers = new ArrayList<>();
        headers.add(new DockerHeader("X-Registry-Auth", encodeAuth));

        try {
            FullHttpResponse response = client.post(uri, headers);
            checkResponse(response);
            String result = response.content().toString(StandardCharsets.UTF_8);
            log.info("docker push 结果: {}", result);
            // TODO push 完成后查找镜像是否存在
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    @Override
    public void pull(String repoTag) throws DockerException {
        String params = "?fromImage=" + repoTag;
        String uri = DockerApi.pullPost + params;

        try {
            FullHttpResponse response = client.post(uri, null);
            checkResponse(response);
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    @Override
    public void rmImage(String repoTag) throws DockerException {
        String uri = DockerApi.rmImageDelete.replace("{}", repoTag);
        try {
            FullHttpResponse response = client.post(uri, null);
            checkResponse(response);
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    @Override
    public String inspectImage(String image) throws DockerException {
        String uri = DockerApi.inspectImageGet.replace("{}", image);
        try {
            FullHttpResponse response = client.get(uri, null);
            if (response != null) {
                return response.content().toString(StandardCharsets.UTF_8);
            } else {
                throw new DockerException("请求 " + uri + " 的响应是 NULL");
            }
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    /* container 操作 */
    @Override
    public String run(String containerName, Config config) throws DockerException {
        stopAndDelete(containerName);
        //config.setImage(repoTag);
        String containerId = create(containerName, config);
        start(containerId);
        return containerId;
    }

    /**
     * 停止并删除容器
     *
     * @param
     * @return
     * @date 2020-03-13 下午10:16
     */
    private void stopAndDelete(String containerName) throws DockerException {
        List<Container> containers = ps(true);
        String name = "/" + containerName;
        for (Container container : containers) {
            Set<String> names = new HashSet<>(container.getNames());
            if (names.contains(name)) {
                String id = container.getId();
                // 匹配任意状态与名字相同的容器
                if ("running".equals(container.getState())) {
                    stop(id);
                }
                rmContainer(id);
            }
        }
    }

    private String create(String containerName, Config config) throws DockerException {
        String uri = DockerApi.createPost + "?name=" + containerName;
        String json = JsonConverter.objectToJson(config);
        try {
            FullHttpResponse response = client.postJson(uri, null, json);
            if (response != null) {
                int statusCode = response.status().code();
                String result = response.content().toString(StandardCharsets.UTF_8);
                if (statusCode == 201) {
                    JsonObject jsonObject = new JsonParser().parse(result).getAsJsonObject();
                    return jsonObject.get("Id").getAsString();
                } else {
                    throw new DockerException(result);
                }
            }
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
        throw new DockerException("请求 " + uri + " 创建容器失败");
    }

    @Override
    public void start(String containerId) throws DockerException {
        String uri = DockerApi.startPost.replace("{}", containerId);
        try {
            FullHttpResponse response = client.post(uri, null);
            checkResponse(response);
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    @Override
    public void restart(String containerId) throws DockerException {
        String uri = DockerApi.restartPost.replace("{}", containerId);
        try {
            FullHttpResponse response = client.post(uri, null);
            checkResponse(response);
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    @Override
    public void stop(String containerId) throws DockerException {
        String uri = DockerApi.stopPost.replace("{}", containerId);
        try {
            FullHttpResponse response = client.post(uri, null);
            checkResponse(response);
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    @Override
    public void rmContainer(String containerId) throws DockerException {
        String uri = DockerApi.rmContainerDelete.replace("{}", containerId);
        try {
            FullHttpResponse response = client.delete(uri, null);
            checkResponse(response);
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    /**
     * 控制台日志
     *
     * @param
     * @return
     * @date 2020-05-19 上午10:26
     */
    @Override
    public List<String> consoleLog(String containerId, String logLevel) throws DockerException {
        try {
            String uri = DockerApi.logsGet.replace("{}", containerId);
            FullHttpResponse response;
            // TODO 处理乱码
            switch (LogLevel.valueOf(logLevel)) {
                case info:
                    // 标准输出
                    String params = "?stdout=true&tail=1000";
                    response = client.get(uri + params, null);
                    if (response != null) {
                        String stdout = response.content().toString(StandardCharsets.UTF_8);
                        return Arrays.asList(stdout.split(System.lineSeparator()));
                    } else {
                        throw new DockerException("请求 " + uri + " 的响应是 NULL");
                    }
                case error:
                    // 标准错误
                    params = "?stderr=true&tail=1000";
                    response = client.get(uri + params, null);
                    if (response != null) {
                        String stderr = response.content().toString(StandardCharsets.UTF_8);
                        return Arrays.asList(stderr.split(System.lineSeparator()));
                    } else {
                        throw new DockerException("请求 " + uri + " 的响应是 NULL");
                    }
                default:
                    throw new DockerException("日志级别 " + logLevel + " 不存在");
            }
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    /**
     * 目录中的文件列表，只返回最近 10 天的日志文件
     * 需要保证日志文件名的格式类似于 2020-01-01.log
     *
     * @param
     * @return
     * @date 2020-05-19 上午10:26
     */
    public List<String> fileList(String containerId, String dirPath) throws DockerException {
        String logDirPath = rootfs(containerId) + dirPath;
        File logDir = new File(logDirPath);
        if (logDir.exists()) {
            List<String> list = Arrays.stream(Objects.requireNonNull(logDir.listFiles()))
                    .map(File::getName)
                    .collect(Collectors.toList());

            // 只返回最近 10 天的日志文件
            if (list.size() > 10) {
                list.sort(Comparator.reverseOrder());
                return list.subList(0, 10);
            }
            return list;
        } else {
            return new ArrayList<>();
        }
    }

    /**
     * 文件日志
     *
     * @param
     * @return
     * @date 2020-05-19 上午10:26
     */
    @Override
    public List<String> fileLog(String containerId, String logPath) throws DockerException {
        String logFilePath = rootfs(containerId) + logPath;
        File logFile = new File(logFilePath);
        if (!logFile.exists()) {
            throw new DockerException(logFilePath + " 文件不存在");
        } else if (logFile.isDirectory()) {
            throw new DockerException(logFilePath + " 是一个文件");
        } else {
            try {
                return textFile.tailRead(logFilePath, 1000);
            } catch (IOException e) {
                throw new DockerException(ExceptionUtil.errorMsg(e));
            }
        }
    }

    @Override
    public List<Container> ps(boolean isAll) throws DockerException {
        String uri = DockerApi.psGet + "?all=" + isAll;
        try {
            FullHttpResponse response = client.get(uri, null);
            String result = checkResponse(response);
            JsonArrayDeserializer<Container> jsonArrayDeserializer = new JsonArrayDeserializer<>();
            return jsonArrayDeserializer.fromJsonArray(result, Container.class);
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    @Override
    public ContainerInfo inspectContainer(String contianerId) throws DockerException {
        String uri = DockerApi.inspectContainerGet.replace("{}", contianerId);
        try {
            FullHttpResponse response = client.get(uri, null);
            if (response != null) {
                String result =  response.content().toString(StandardCharsets.UTF_8);
                return JsonConverter.jsonToObject(result, ContainerInfo.class);
            } else {
                throw new DockerException("请求 " + uri + " 的响应是 NULL");
            }
        } catch (IOException | InterruptedException e) {
            throw new DockerException(ExceptionUtil.errorMsg(e));
        }
    }

    @Override
    public String rootfs(String containerId) throws DockerException {
        ContainerInfo containerInfo = inspectContainer(containerId);
        // 容器停止后 mergedDir 不再存在
        return containerInfo.getGraphDriver().getData().getMergedDir();
    }

    // TODO 待实现 docker cp
    public void archive(String containerId) {
        String uri = "http://v1.4.0/containers/32487cddf5d5/archive?path=/";
        try {
            FullHttpResponse response = client.get(uri, null);
            if (response != null) {
                String result =  response.content().toString(StandardCharsets.UTF_8);
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
