/*
 * www.msxf.com Inc.
 * Copyright (c) 2017 All Rights Reserved
 */
package com.falcon.suitagent.util;
//             ,%%%%%%%%,
//           ,%%/\%%%%/\%%
//          ,%%%\c "" J/%%%
// %.       %%%%/ o  o \%%%
// `%%.     %%%%    _  |%%%
//  `%%     `%%%%(__Y__)%%'
//  //       ;%%%%`\-/%%%'
// ((       /  `%%%%%%%'
//  \\    .'          |
//   \\  /       \  | |
//    \\/攻城狮保佑) | |
//     \         /_ | |__
//     (___________)))))))                   `\/'
/*
 * 修订记录:
 * [email protected] 2017-08-04 16:53 创建
 */

import com.falcon.suitagent.config.AgentConfiguration;
import com.falcon.suitagent.vo.docker.ContainerProcInfoToHost;
import com.google.common.collect.ImmutableMap;
import com.spotify.docker.client.DefaultDockerClient;
import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.messages.AttachedNetwork;
import com.spotify.docker.client.messages.Container;
import com.spotify.docker.client.messages.ContainerInfo;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import static com.falcon.suitagent.util.CacheByTimeUtil.getCache;
import static com.falcon.suitagent.util.CacheByTimeUtil.setCache;

/**
 * @author [email protected]
 */
@Slf4j
public class DockerUtil {

    private static final Byte[] LockForGetAllHostContainerId = new Byte[0];
    private static final Byte[] LockForGetAllHostContainerProcInfos = new Byte[0];

    private static final String PROC_HOST_VOLUME = "/proc_host";
    private static DockerClient docker = null;
    static {
        if (AgentConfiguration.INSTANCE.isDockerRuntime()){
            try {
                docker = new DefaultDockerClient("unix:///var/run/docker.sock");
            } catch (Exception e) {
                log.error("docker client初始化失败,可能未挂在/var/run目录或无文件/var/run/docker.sock的访问权限",e);
                throw e;
            }
        }
    }

    public static void closeDockerClient(){
        if (docker != null){
            docker.close();
        }
    }

    /**
     * 获取容器信息
     * @param containerId
     * @return
     */
    public static ContainerInfo getContainerInfo(String containerId){
        String cacheKey = "containerInfoCacheKey" + containerId;
        final ContainerInfo[] containerInfo = {(ContainerInfo) getCache(cacheKey)};
        if (containerInfo[0] != null){
            return containerInfo[0];
        }else {
            synchronized (containerId.intern()) {
                try {
                    int timeOut = 45;
                    final BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(1);
                    //阻塞队列异步执行
                    ExecuteThreadUtil.execute(() -> {
                        try {
                            containerInfo[0] = docker.inspectContainer(containerId);
                            setCache(cacheKey, containerInfo[0]);
                            blockingQueue.offer(containerInfo[0]);
                        } catch (Throwable t) {
                            blockingQueue.offer(t);
                        }
                    });

                    //超时45秒
                    Object result = BlockingQueueUtil.getResult(blockingQueue, timeOut, TimeUnit.SECONDS);
                    blockingQueue.clear();

                    if (result instanceof ContainerInfo) {
                        return (ContainerInfo) result;
                    }else if (result == null) {
                        log.error("docker 容器Info获取{}秒超时:{}",timeOut,containerId);
                        return null;
                    }else if (result instanceof Throwable) {
                        log.error("docker 容器Info获取异常",result);
                        return null;
                    }else {
                        log.error("未知结果类型:{}",result);
                        return null;
                    }
                } catch (Exception e) {
                    log.error("",e);
                    return null;
                }
            }
        }
    }

    /**
     * 获取容器列表
     * @param containersParam
     * @return
     */
    public static List<Container> getContainers(DockerClient.ListContainersParam containersParam) {
        String cacheKey = "getContainer" + containersParam.value();
        final List<Container> containers = (List<Container>) CacheByTimeUtil.getCache(cacheKey);
        if (containers != null) {
            return containers;
        }

        try {
            int timeOut = 45;
            final BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(1);
            //阻塞队列异步执行
            ExecuteThreadUtil.execute(() -> {
                try {
                    List<Container> containerList = docker.listContainers(containersParam);
                    setCache(cacheKey, containerList);
                    blockingQueue.offer(containerList);
                } catch (Throwable t) {
                    blockingQueue.offer(t);
                }
            });

            //超时
            Object result = BlockingQueueUtil.getResult(blockingQueue, timeOut, TimeUnit.SECONDS);
            blockingQueue.clear();

            if (result instanceof List) {
                return (List<Container>) result;
            }else if (result == null) {
                log.error("docker 容器 List 获取{}秒超",timeOut);
                return new ArrayList<>();
            }else if (result instanceof Throwable) {
                log.error("docker 容器 List 获取异常",result);
                return new ArrayList<>();
            }else {
                log.error("未知结果类型:{}",result);
                return new ArrayList<>();
            }
        } catch (Exception e) {
            log.error("",e);
            return new ArrayList<>();
        }

    }

    /**
     * 获取主机上所有运行容器的proc信息
     * @return
     */
    public static List<ContainerProcInfoToHost> getAllHostContainerProcInfos(){
        String cacheKey = "ALL_HOST_CONTAINER_PROC_INFOS";
        List<ContainerProcInfoToHost> procInfoToHosts = (List<ContainerProcInfoToHost>) getCache(cacheKey);
        if (procInfoToHosts != null){
            //返回缓存数据
            return procInfoToHosts;
        }else {
            procInfoToHosts = new ArrayList<>();
        }
        synchronized (LockForGetAllHostContainerProcInfos){
            try {
                List<Container> containers = getContainers(DockerClient.ListContainersParam.withStatusRunning());
                for (Container container : containers) {
                    ContainerInfo info = getContainerInfo(container.id());
                    if (info != null) {
                        String pid = String.valueOf(info.state().pid());
                        procInfoToHosts.add(new ContainerProcInfoToHost(container.id(),PROC_HOST_VOLUME + "/" + pid + "/root",pid));
                    }
                }
            } catch (Exception e) {
                log.error("",e);
            }
        }
        if (!procInfoToHosts.isEmpty()) {
            //设置缓存
            setCache(cacheKey,procInfoToHosts);
        }
        return procInfoToHosts;
    }

    /**
     * 获取Java应用容器的应用名称
     * 注:
     * 必须通过docker run命令的-e参数执行应用名,例如 docker run -e "appName=suitAgent"
     * @param containerId
     * 容器id
     * @return
     * 若未指定应用名称或获取失败返回null
     */
    public static String getJavaContainerAppName(String containerId) throws InterruptedException {
        String cacheKey = "appName" + containerId;
        String v = (String) getCache(cacheKey);
        if (StringUtils.isNotEmpty(v)) {
            return v;
        }
        try {
            ContainerInfo containerInfo = getContainerInfo(containerId);
            if (containerInfo != null){
                List<String> env = containerInfo.config().env();
                if (env != null){
                    for (String s : env) {
                        String[] split = s.split(s.contains("=") ? "=" : ":");
                        if (split.length == 2){
                            String key = split[0];
                            String value = split[1];
                            if ("appName".equals(key)){
                                setCache(cacheKey,value);
                                return value;
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("",e);
        }

        return null;
    }


    /**
     * 容器网络模式是否为host模式
     * @param containerId
     * @return
     */
    public static boolean isHostNet(String containerId){
        String cacheKey = "isHostNet" + containerId;
        Boolean v = (Boolean) getCache(cacheKey);
        if (v != null){
            return v;
        }
        Boolean value = false;
        try {
            ContainerInfo containerInfo = getContainerInfo(containerId);
            if (containerInfo != null) {
                ImmutableMap<String, AttachedNetwork> networks =  containerInfo.networkSettings().networks();
                if (networks != null && !networks.isEmpty()){
                    value = networks.get("host") != null && StringUtils.isNotEmpty(networks.get("host").ipAddress());
                    setCache(cacheKey,value);
                }else {
                    log.warn("容器{}无Networks配置",containerInfo.name());
                }
            }
        } catch (Exception e) {
            log.error("",e);
        }
        return value;
    }

    /**
     * 获取容器主机名
     * @param containerId
     * @return
     */
    public static String getHostName(String containerId){
        try {
            ContainerInfo containerInfo = getContainerInfo(containerId);
            if (containerInfo != null) {
                return containerInfo.config().hostname();
            }
        } catch (Exception e) {
            log.error("",e);
        }
        return "";
    }

    /**
     * 获取容器IP地址
     * @param containerId
     * 容器ID
     * @return
     * 1、获取失败返回null
     * 2、host网络模式直接返回宿主机IP
     */
    public static String getContainerIp(String containerId){
        String cacheKey = "containerIp" + containerId;
        String v = (String) getCache(cacheKey);
        if (StringUtils.isNotEmpty(v)) {
            return v;
        }
        try {
            if (isHostNet(containerId)){
                return HostUtil.getHostIp();
            }
            ContainerInfo containerInfo = getContainerInfo(containerId);
            if (containerInfo != null) {
                ImmutableMap<String, AttachedNetwork> networks =  containerInfo.networkSettings().networks();
                if (networks != null && !networks.isEmpty()){
                    String ip = networks.get(networks.keySet().asList().get(0)).ipAddress();
                    setCache(cacheKey,ip);
                    return ip;
                }else {
                    log.warn("容器{}无Networks配置",containerInfo.name());
                }
            }
        } catch (Exception e) {
            log.error("",e);
        }

        return null;
    }

    /**
     * 判断本地所有容器(所有状态)中,是否存在容器id前12位字符串包含在指定的名称中
     * @param name
     * @return
     */
    public static boolean has12ContainerIdInName(String name) {
        if (StringUtils.isEmpty(name)){
            return false;
        }
        try {
            for (String id : getAllHostContainerId()) {
                if (name.contains(id.substring(0,12))){
                    return true;
                }
            }
        } catch (Exception e) {
            log.error("",e);
            return false;
        }
        return false;
    }


    private static List<String> getAllHostContainerId(){
        String cacheKey = "allHostContainerIds";
        int cacheTime = 28;//28秒缓存周期,保证一次采集job只需要访问一次docker即可

        List<String> ids = (List<String>) getCache(cacheKey);
        if (ids != null){
            return ids;
        }else {
            ids = new ArrayList<>();
        }
        synchronized (LockForGetAllHostContainerId){
            try {
                List<Container> containerList = getContainers(DockerClient.ListContainersParam.allContainers());
                for (Container container : containerList) {
                    ids.add(container.id());
                }
            } catch (Exception e) {
                log.error("",e);
            }
        }
        setCache(cacheKey,ids,cacheTime);
        return ids;
    }

}