package io.wdd.func.xray.service;

import io.wdd.common.handler.MyRuntimeException;
import io.wdd.func.xray.beans.node.ProxyNode;
import io.wdd.func.xray.beans.node.XrayConfigInfo;
import io.wdd.func.xray.beans.xray.RoutingObject;
import io.wdd.func.xray.beans.xray.RuleObject;
import io.wdd.func.xray.beans.xray.XrayConfig;
import io.wdd.func.xray.beans.xray.protocol.inbound.vmess.ClientObject;
import io.wdd.func.xray.beans.xray.protocol.inbound.vmess.InboundConfigurationObject;
import io.wdd.func.xray.beans.xray.protocol.inbound.vmess.VMESS;
import io.wdd.func.xray.beans.xray.protocol.outbound.Freedom;
import io.wdd.func.xray.beans.xray.protocol.outbound.OutboundConfigurationObject;
import io.wdd.func.xray.beans.xray.protocol.outbound.VmessOutSettings;
import io.wdd.func.xray.beans.xray.share.VmessShare;
import io.wdd.func.xray.beans.xray.transport.InboundObject;
import io.wdd.func.xray.beans.xray.transport.OutboundObject;
import io.wdd.func.xray.persisit.XrayDBOperator;
import io.wdd.func.xray.persisit.XrayOssOperator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

import static io.wdd.func.xray.beans.config.InboundVmessHTTPTemplateClass.ListenAddress;
import static io.wdd.func.xray.beans.config.InboundVmessTCPTemplateClass.InboundVmessTCPTemplate;
import static io.wdd.func.xray.beans.config.LogTemplateClass.LogTemplate;
import static io.wdd.func.xray.beans.config.OutboundVmessHTTPTemplateClass.OutboundBlackHole;
import static io.wdd.func.xray.beans.config.OutboundVmessHTTPTemplateClass.OutboundFree;
import static io.wdd.func.xray.beans.config.OutboundVmessTCPTemplateClass.OutboundVmessTCPTemplate;
import static io.wdd.func.xray.persisit.cache.ProxyNodeCache.*;
import static io.wdd.func.xray.service.XrayConfigPersistor.*;

@Service
@Slf4j
public class XrayCoreServiceImpl implements XrayCoreService {

    private static final String ProxyChainSplitor = "->";
    @Resource
    XrayConfigPersistor xrayConfigPersistor;
    @Resource
    XrayDBOperator xrayDBOperator;

    @Resource
    XrayCallAgent xrayCallAgent;

    @Resource
    XrayOssOperator xrayOssOperator;

    /**
     * 根据全部的代理链，获取到 独立的节点Set
     *
     * @param allNetworkPathList
     * @return
     */
    private static Set<ProxyNode> getAllProxyNodeSet(List<List<ProxyNode>> allNetworkPathList, boolean includeSubscribeNode) {
        // 拿到所有的proxyNode
        Set<ProxyNode> proxyNodeSet = allNetworkPathList
                .stream()
                .flatMap(
                        networkPathList -> networkPathList.stream()
                )
                .collect(Collectors.toSet());
        // 增加虚拟节点
        if (includeSubscribeNode) {
            proxyNodeSet.add(
                    PROXY_NODE_TOPIC_NAME_MAP.get(
                            SUBSCRIBE_NODE_NAME
                    )
            );
        }
        return proxyNodeSet;
    }

    @Override
    public List<List<ProxyNode>> transformProxyChainString(List<String> proxyChainList, boolean isTopicName) {

        return proxyChainList
                .stream()
                .map(
                        proxyChain -> Arrays
                                .stream(proxyChain.split(ProxyChainSplitor))
                                .map(
                                        // 此处获取到的就是 每一个节点的名称
                                        // AgentName 或者  AgentTopicName
                                        agentKindOfName -> {
                                            ProxyNode targetNode = null;

                                            if (isTopicName) {
                                                targetNode = PROXY_NODE_TOPIC_NAME_MAP.get(agentKindOfName);
                                            } else {
                                                targetNode = PROXY_NODE_MAP.get(agentKindOfName);
                                            }

                                            Assert.notNull(
                                                    targetNode,
                                                    "输入的代理链字符串有误！ 请检查！ => " + agentKindOfName
                                            );
                                            return targetNode;
                                        }
                                )
                                .collect(Collectors.toList())
                )
                .collect(Collectors.toList());

    }

    @Override
    public List<XrayConfigInfo> generateXrayConfigFromNodeList(List<List<ProxyNode>> allNetworkPathList) {

        // 需要根据所有的交叉链路进行计算
        // A -> B -> C -> D
        //      B -> D
        // A -> B -> F
        // A点需要具有  2对inbound和outbound对
        // B点需要具有  3对inbound和outbound对
        // C点需要具有  2对inbound和outbound对
        // D点需要具有  2对inbound和outbound对
        // F点需要具有  1对inbound和outbound对


        // 解析所有的Node对

        // 返回每一条的结果至于 Map中 key   =>   shanghai->seoul2->tokyo2
        //                        value =>   xray配置信息
        // HashMap<String, String> interfaceMap = new HashMap<>();

        // 最终Xray Json配置应该放置于 Map中 key => ProxyNode
        //                              value => Xray Config Json
        // HashMap<ProxyNode, XrayConfig> resultMap = new HashMap<>();

        // 清除tmp目录时候使用的一个锁
        int currentVersion = cleanVersion.get();

        // 记录每条代理链的端口号
        HashMap<String, Integer> chainPortMap = new HashMap<>();

        // 需要事先清空缓存
        manualUpdateProxyNodeListCache();

        // 生成代理链
        allNetworkPathList
                .stream()
                .forEach(
                        networkPathList -> {
                            // 为每一条链生成端口
                            int chainPort = XRAY_PORT_POOL.decrementAndGet();

                            // 为每条链生成相应的tag
                            // 形式为 Shanghai-amd64-01->Seoul-amd64-04
                            String chainTag = generatePathTag(networkPathList);

                            // 每条链单独生成Xray Config配置文件
                            generateXrayJsonSinglePath(
                                    chainPort,
                                    networkPathList,
                                    currentVersion,
                                    chainTag
                            );

                            // 为每条代理链 缓存 对应端口
                            chainPortMap.put(
                                    chainTag,
                                    chainPort
                            );
                        }
                );

        // 生成订阅地址链接
        List<VmessShare> vmessShareList = generateSubscribeString(
                allNetworkPathList,
                chainPortMap
        );

        log.debug(
                "vmessShareList is => {}",
                vmessShareList
        );

        // 开始持久化订阅
        xrayConfigPersistor.subPersist(vmessShareList);

        Set<ProxyNode> proxyNodeSet = getAllProxyNodeSet(
                allNetworkPathList,
                true
        );

        // 返回所有的配置
        return proxyNodeSet
                .stream()
                .map(
                        ProxyNode::getXrayConfigInfo
                )
                .collect(Collectors.toList());

    }

    @Override
    public HashMap<String, ProxyNode> manualUpdateProxyNodeListCache() {

        // 重新手动获取所有的ProxyNode缓存信息
        xrayDBOperator.CacheAllProxyNodeInfo();

        // 清空 XRAY_PORT_POOL
        XRAY_PORT_POOL.set(XRAY_PORT_MAX);

        return PROXY_NODE_TOPIC_NAME_MAP;
    }

    @Override
    public List<VmessShare> generateSubscribeString(List<List<ProxyNode>> allNetworkPathList, HashMap<String, Integer> chainPortMap) {

        // 运行到此时，说明配置文件已经生成了, 根据每条链生成订阅定西
        return allNetworkPathList
                .stream()
                .map(
                        networkPathList -> {

                            // 每条链需要生成相应的唯一标识
                            String chainTag = generatePathTag(networkPathList);

                            // 拿到每条链的头节点
                            ProxyNode headNode = networkPathList.get(0);

                            List<InboundObject> inboundObjectList = headNode
                                    .getXrayConfigInfo()
                                    .getXrayConfig()
                                    .getInbounds();

                            // 构造Inbound Object
                            VMESS realInboundObject = (VMESS) inboundObjectList
                                    .stream()
                                    .filter(
                                            inboundObject -> inboundObject
                                                    .getPort()
                                                    .equals(
                                                            // 拿到每条链的唯一标识
                                                            chainPortMap.get(
                                                                    chainTag
                                                            ))
                                    )
                                    .findFirst()
                                    .get();

                            return convertInboundToShare(
                                    realInboundObject,
                                    headNode
                            );

                        }
                )
                .collect(Collectors.toList());

    }

    @Override
    public List<XrayConfigInfo> ProxyChainUpdateProcedure(List<String> proxyChainList, boolean isTopicName) {

        // 解析得到全部的节点
        List<List<ProxyNode>> allProxyChainNodeList = this.transformProxyChainString(
                proxyChainList,
                isTopicName
        );

        // 生成所有的配置
        List<XrayConfigInfo> xrayConfigInfoList = this.generateXrayConfigFromNodeList(allProxyChainNodeList);

        // 获取到全部的节点
        Set<ProxyNode> allProxyNodeSet = getAllProxyNodeSet(
                allProxyChainNodeList,
                false
        );

        // 上传至OSS中
        xrayOssOperator.uploadXrayConfigToOSS(allProxyNodeSet);
        xrayOssOperator.uploadSubscribeFileToOSS();

        // 执行Xray更新操作`
        xrayCallAgent.buildXrayUpdateCommandAndCallAgent(allProxyNodeSet);


        return xrayConfigInfoList;
    }


    /**
     * 目前只进行 VMESS协议的转换
     *
     * @param realInboundObject
     * @param headNode
     * @return
     */
    private VmessShare convertInboundToShare(VMESS realInboundObject, ProxyNode headNode) {

        // 通常这里只有一个配置
        ClientObject clientObject = realInboundObject
                .getSettings()
                .getClients()
                .get(0);


        return VmessShare
                .builder()
                .v("2")
                .ps(realInboundObject.getTag())
                .add(headNode.getPublicIPv4())
                .port(String.valueOf(realInboundObject.getPort()))
                .id(clientObject.getId())
                .aid(String.valueOf(clientObject.getAlterId()))
                .scy("auto")
                .net("tcp")
                .type("http")
                .host("www.baidu.com,www.google.com,www.bing.com,www.github.com")
                .path("/")
                .tls("")
                .sni("")
                .alpn("")
                .build();

    }

    /**
     * 生成一条代理链
     *
     * @param chainPort       代理连使用的端口
     * @param networkPathList 代理链上的每一个服务器节点
     * @param currentVersion
     * @param chainTag        代理链的tag
     */
    private void generateXrayJsonSinglePath(int chainPort, List<ProxyNode> networkPathList, int currentVersion, String chainTag) {

        int pathLength = networkPathList.size();

        // 采用 VMESS + websocket的形式形成 链状代理
        // 由于 Vlss+XTLS的形式形成 链状结构

        // 通用内容生成
        String uuid = UUID
                .randomUUID()
                .toString();
        ClientObject clientObject = ClientObject
                .builder()
                .id(uuid)
                .level(0)
                // 为了进一步防止被探测，一个用户可以在主 ID 的基础上，再额外生成多个 ID。这里只需要指定额外的 ID 的数量，推荐值为 0 代表启用 VMessAEAD
                .alterId(0)
                .email(chainTag + "@octopus.io")
                .build();

        for (int pos = 0; pos < pathLength; pos++) {

            ProxyNode proxyNode = networkPathList.get(pos);

            // 构造 xrayConfig
            XrayConfig xrayConfig = doBuildXrayConfig(
                    chainTag,
                    clientObject,
                    chainPort,
                    networkPathList,
                    pos
            );

            // 在 缓存层 持久化 Xray生成的文件信息
            XrayConfigInfo xrayConfigInfo = xrayConfigPersistor.persist(
                    xrayConfig,
                    currentVersion,
                    proxyNode
            );
            proxyNode.setXrayConfigInfo(xrayConfigInfo);
        }

    }


    /**
     * 实际身长Xray的配置
     *
     * @param chainTag
     * @param clientObject
     * @param chainPort
     * @param networkPathList
     * @param pos
     * @return
     */
    private XrayConfig doBuildXrayConfig(String chainTag, ClientObject clientObject, int chainPort, List<ProxyNode> networkPathList, int pos) {

        // 需要从现有的XrayConfig基础上进行生成
        // 尝试从 缓存层获取
        XrayConfig xrayConfig = networkPathList
                .get(pos)
                .getXrayConfigInfo()
                .getXrayConfig();
        if (ObjectUtils.isEmpty(xrayConfig)) {
            xrayConfig = new XrayConfig();
        }

        // 设置Log属性
        xrayConfig.setLog(LogTemplate);

        // 初始化 InBound
        buildInbound(
                xrayConfig,
                clientObject,
                chainPort,
                chainTag
        );

        // 中间节点,需要有特定的输出
        // 设置 outbounds的信息
        buildOutbound(
                xrayConfig,
                networkPathList,
                pos,
                clientObject,
                chainTag,
                chainPort
        );

        // 设置 路由信息
        buildRouting(
                xrayConfig,
                chainTag
        );

        return xrayConfig;
    }

    private void buildOutboundFree(XrayConfig xrayConfig, String chainTag) {

        OutboundObject freeOut = new Freedom();
        freeOut.setTag(chainTag);

        xrayConfig.setOutbounds(new ArrayList<>(
                List.of(freeOut)
        ));

    }

    private void buildRouting(XrayConfig xrayConfig, String chainTag) {

        // 从缓存中获取
        RoutingObject routingObject = xrayConfig.getRouting();
        if (ObjectUtils.isEmpty(routingObject)) {
            routingObject = new RoutingObject();
            routingObject.setDomainStrategy("IPIfNonMatch");
            xrayConfig.setRouting(routingObject);
        }

        RuleObject ruleObject = RuleObject
                .builder()
                .type("field")
                .inboundTag(List.of(chainTag))
                .outboundTag(chainTag)
                .build();

        List<RuleObject> ruleObjectList = routingObject.getRules();
        if (CollectionUtils.isEmpty(ruleObjectList)) {
            ruleObjectList = new ArrayList<>();
            routingObject.setRules(ruleObjectList);
        }

        ruleObjectList.add(ruleObject);
    }

    private void buildOutbound(XrayConfig xrayConfig, List<ProxyNode> networkPathList, int pos, ClientObject clientObject, String chainTag, Integer chainPort) {

        // 从缓存中获取
        List<OutboundObject> outboundObjectList = xrayConfig.getOutbounds();
        if (CollectionUtils.isEmpty(outboundObjectList)) {
            outboundObjectList = new ArrayList<>();
            xrayConfig.setOutbounds(outboundObjectList);
        }

        int ChainLastPosIndex = networkPathList.size() - 1;

        if (pos == ChainLastPosIndex) {
            // 代理链的最后一位
            if (!outboundObjectList.contains(OutboundFree)) {
                outboundObjectList.add(OutboundFree);
            }
            if (!outboundObjectList.contains(OutboundBlackHole)) {
                outboundObjectList.add(OutboundBlackHole);
            }
            return;
        }

        // 中间节点，需要进行特定的构建
        io.wdd.func.xray.beans.xray.protocol.outbound.VMESS vmessOutbound = new io.wdd.func.xray.beans.xray.protocol.outbound.VMESS();

        // 深拷贝的问题
        ClientObject realClientObject = new ClientObject();

        try {

            BeanUtils.copyProperties(
                    vmessOutbound,
                    OutboundVmessTCPTemplate
            );

            // 解决深拷贝的问题
            BeanUtils.copyProperties(
                    realClientObject,
                    clientObject
            );

        } catch (IllegalAccessException | InvocationTargetException e) {
            log.error("复制模板OutBound失败！");
            throw new MyRuntimeException(e);
        }

        // 设置Tag信息
        vmessOutbound.setTag(chainTag);

        //
        OutboundConfigurationObject.ServerObject serverObject = new OutboundConfigurationObject.ServerObject();

        // 出口的IP应该为下一节点
        serverObject.setAddress(networkPathList
                                        .get(pos + 1)
                                        .getPublicIPv4());

        // 端口
        serverObject.setPort(chainPort);
        // 用户
        serverObject.setUsers(
                List.of(realClientObject)
        );

        // 直接新建一个Settings的对象
        VmessOutSettings vmessOutSettings = new VmessOutSettings();
        vmessOutSettings.setVnext(
                List.of(
                        serverObject
                )
        );

        // 设置 Settings对象
        vmessOutbound.setSettings(vmessOutSettings);


        outboundObjectList.add(vmessOutbound);
    }

    private void buildInbound(XrayConfig xrayConfig, ClientObject clientObject, Integer chainPort, String chainTag) {

        // 从缓存中获取
        List<InboundObject> inboundObjectList = xrayConfig.getInbounds();
        if (CollectionUtils.isEmpty(inboundObjectList)) {
            inboundObjectList = new ArrayList<>();
            xrayConfig.setInbounds(inboundObjectList);
        }

        VMESS inboundObject = new VMESS();

        try {
            BeanUtils.copyProperties(
                    inboundObject,
                    InboundVmessTCPTemplate
            );
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        inboundObject.setTag(chainTag);

        // todo port怎么办
        inboundObject.setPort(chainPort);
        // 设置 listen地址
        inboundObject.setListen(ListenAddress);

        // 配置用户信息
        InboundConfigurationObject inboundConfigurationObject = InboundConfigurationObject
                .builder()
                .clients(List.of(clientObject))
                .build();

        inboundObject.setSettings(inboundConfigurationObject);

        // 增加进去
        inboundObjectList.add(inboundObject);
    }

    /**
     * Shanghai-amd64-01->Seoul-amd64-04
     *
     * @param networkPathList 网络路径节点
     * @return 路径Tag
     */
    private String generatePathTag(List<ProxyNode> networkPathList) {

        StringBuilder sb = new StringBuilder();
        networkPathList
                .stream()
                .forEach(
                        networkPath -> {
                            sb.append(networkPath.getAgentName());
                            sb.append("->");
                        }
                );

        // 去掉末尾的 ->
        String s = sb.toString();

        return s.substring(
                0,
                s.length() - 2
        );
    }
}
