/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.dashboard.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.util.StringUtil;

import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterGroupEntity;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.ConnectionGroupVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.AppClusterClientStateWrapVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.AppClusterServerStateWrapVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterClientStateVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterServerStateVO;
import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO;

/**
 * @author Eric Zhao
 * @since 1.4.1
 */
public final class ClusterEntityUtils {

    public static List<AppClusterServerStateWrapVO> wrapToAppClusterServerState(
        List<ClusterUniversalStatePairVO> list) {
        if (list == null || list.isEmpty()) {
            return new ArrayList<>();
        }
        Map<String, AppClusterServerStateWrapVO> map = new HashMap<>();
        Set<String> tokenServerSet = new HashSet<>();
        // Handle token servers that belong to current app.
        for (ClusterUniversalStatePairVO stateVO : list) {
            int mode = stateVO.getState().getStateInfo().getMode();

            if (mode == ClusterStateManager.CLUSTER_SERVER) {
                String ip = stateVO.getIp();
                String serverId = ip + '@' + stateVO.getCommandPort();
                ClusterServerStateVO serverStateVO = stateVO.getState().getServer();
                map.computeIfAbsent(serverId, v -> new AppClusterServerStateWrapVO()
                    .setId(serverId)
                    .setIp(ip)
                    .setPort(serverStateVO.getPort())
                    .setState(serverStateVO)
                    .setBelongToApp(true)
                    .setConnectedCount(serverStateVO.getConnection().stream()
                        .mapToInt(ConnectionGroupVO::getConnectedCount)
                        .sum()
                    )
                );
                tokenServerSet.add(ip + ":" + serverStateVO.getPort());
            }
        }
        // Handle token servers from other app.
        for (ClusterUniversalStatePairVO stateVO : list) {
            int mode = stateVO.getState().getStateInfo().getMode();

            if (mode == ClusterStateManager.CLUSTER_CLIENT) {
                ClusterClientStateVO clientState = stateVO.getState().getClient();
                if (clientState == null) {
                    continue;
                }
                String serverIp = clientState.getClientConfig().getServerHost();
                int serverPort = clientState.getClientConfig().getServerPort();
                if (tokenServerSet.contains(serverIp + ":" + serverPort)) {
                    continue;
                }
                // We are not able to get the commandPort of foreign token server directly.
                String serverId = String.format("%s:%d", serverIp, serverPort);
                map.computeIfAbsent(serverId, v -> new AppClusterServerStateWrapVO()
                    .setId(serverId)
                    .setIp(serverIp)
                    .setPort(serverPort)
                    .setBelongToApp(false)
                );
            }
        }
        return new ArrayList<>(map.values());
    }

    public static List<AppClusterClientStateWrapVO> wrapToAppClusterClientState(
        List<ClusterUniversalStatePairVO> list) {
        if (list == null || list.isEmpty()) {
            return new ArrayList<>();
        }
        Map<String, AppClusterClientStateWrapVO> map = new HashMap<>();
        for (ClusterUniversalStatePairVO stateVO : list) {
            int mode = stateVO.getState().getStateInfo().getMode();

            if (mode == ClusterStateManager.CLUSTER_CLIENT) {
                String ip = stateVO.getIp();
                String clientId = ip + '@' + stateVO.getCommandPort();
                ClusterClientStateVO clientStateVO = stateVO.getState().getClient();
                map.computeIfAbsent(clientId, v -> new AppClusterClientStateWrapVO()
                    .setId(clientId)
                    .setIp(ip)
                    .setState(clientStateVO)
                    .setCommandPort(stateVO.getCommandPort())
                );
            }
        }
        return new ArrayList<>(map.values());
    }

    public static List<ClusterGroupEntity> wrapToClusterGroup(List<ClusterUniversalStatePairVO> list) {
        if (list == null || list.isEmpty()) {
            return new ArrayList<>();
        }
        Map<String, ClusterGroupEntity> map = new HashMap<>();
        for (ClusterUniversalStatePairVO stateVO : list) {
            int mode = stateVO.getState().getStateInfo().getMode();
            String ip = stateVO.getIp();
            if (mode == ClusterStateManager.CLUSTER_SERVER) {
                String serverAddress = getIp(ip);
                int port = stateVO.getState().getServer().getPort();
                map.computeIfAbsent(serverAddress, v -> new ClusterGroupEntity()
                    .setBelongToApp(true).setMachineId(ip + '@' + stateVO.getCommandPort())
                    .setIp(ip).setPort(port)
                );
            }
        }
        for (ClusterUniversalStatePairVO stateVO : list) {
            int mode = stateVO.getState().getStateInfo().getMode();
            String ip = stateVO.getIp();
            if (mode == ClusterStateManager.CLUSTER_CLIENT) {
                String targetServer = stateVO.getState().getClient().getClientConfig().getServerHost();
                Integer targetPort = stateVO.getState().getClient().getClientConfig().getServerPort();
                if (StringUtil.isBlank(targetServer) || targetPort == null || targetPort <= 0) {
                    continue;
                }

                ClusterGroupEntity group = map.computeIfAbsent(targetServer,
                    v -> new ClusterGroupEntity()
                        .setBelongToApp(true).setMachineId(targetServer)
                        .setIp(targetServer).setPort(targetPort)
                );
                group.getClientSet().add(ip + '@' + stateVO.getCommandPort());
            }
        }
        return new ArrayList<>(map.values());
    }

    private static String getIp(String str) {
        if (str.contains(":")) {
            return str.split(":")[0];
        }
        return str;
    }

    private ClusterEntityUtils() {}
}

@@ -5872,32 +5872,99 @@
 er().getPort();%0A
+                String targetAddress = serverAddress + %22:%22 + port;%0A
                 
@@ -5983,22 +5983,22 @@
 fAbsent(
-server
+target
 Address,
@@ -6786,24 +6786,95 @@
           %7D%0A
+                String targetAddress = targetServer + %22:%22 + targetPort;
 %0A           
@@ -6931,22 +6931,23 @@
 t(target
-Server
+Address
 ,%0A      

Fix the bug that removing token servers may fail when there are servers with the same IP (#2591)