/*
 *   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.nacos.client.config;

import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigType;
import com.alibaba.nacos.api.config.listener.ConfigFuzzyWatchChangeEvent;
import com.alibaba.nacos.api.config.listener.FuzzyWatchEventWatcher;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.client.config.filter.impl.ConfigResponse;
import com.alibaba.nacos.client.config.impl.ClientWorker;
import com.alibaba.nacos.client.config.impl.ConfigFuzzyWatchContext;
import com.alibaba.nacos.client.config.impl.ConfigServerListManager;
import com.alibaba.nacos.client.config.impl.ConfigTransportClient;
import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor;
import com.alibaba.nacos.client.env.NacosClientProperties;
import com.alibaba.nacos.common.utils.FuzzyGroupKeyPattern;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;

import static com.alibaba.nacos.api.common.Constants.ALL_PATTERN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class NacosConfigServiceTest {
    
    private NacosConfigService nacosConfigService;
    
    private ClientWorker mockWoker;
    
    private void setFinal(Field field, Object ins, Object newValue) throws Exception {
        field.setAccessible(true);
        field.set(ins, newValue);
        field.setAccessible(false);
        
    }
    
    @BeforeEach
    void mock() throws Exception {
        final Properties properties = new Properties();
        properties.put("serverAddr", "1.1.1.1");
        nacosConfigService = new NacosConfigService(properties);
        mockWoker = Mockito.mock(ClientWorker.class);
        setFinal(NacosConfigService.class.getDeclaredField("worker"), nacosConfigService, mockWoker);
    }
    
    @AfterEach
    void clean() {
        LocalConfigInfoProcessor.cleanAllSnapshot();
    }
    
    @Test
    void testGetConfigFromServer() throws NacosException {
        final String dataId = "1";
        final String group = "2";
        final String tenant = "public";
        final int timeout = 3000;
        ConfigResponse response = new ConfigResponse();
        response.setContent("aa");
        response.setConfigType("bb");
        Mockito.when(mockWoker.getServerConfig(dataId, group, tenant, timeout, false)).thenReturn(response);
        final String config = nacosConfigService.getConfig(dataId, group, timeout);
        assertEquals("aa", config);
        Mockito.verify(mockWoker, Mockito.times(1)).getServerConfig(dataId, group, tenant, timeout, false);
        
    }
    
    @Test
    void testGetConfigFromFailOver() throws NacosException {
        final String dataId = "1failover";
        final String group = "2";
        final String tenant = "public";
        
        MockedStatic<LocalConfigInfoProcessor> localConfigInfoProcessorMockedStatic = Mockito.mockStatic(
                LocalConfigInfoProcessor.class);
        try {
            String contentFailOver = "failOverContent" + System.currentTimeMillis();
            localConfigInfoProcessorMockedStatic.when(
                    () -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant)))
                    .thenReturn(contentFailOver);
            final int timeout = 3000;
            
            final String config = nacosConfigService.getConfig(dataId, group, timeout);
            assertEquals(contentFailOver, config);
        } finally {
            localConfigInfoProcessorMockedStatic.close();
        }
    }
    
    @Test
    void testGetConfigFromLocalCache() throws NacosException {
        final String dataId = "1localcache";
        final String group = "2";
        final String tenant = "public";
        
        MockedStatic<LocalConfigInfoProcessor> localConfigInfoProcessorMockedStatic = Mockito.mockStatic(
                LocalConfigInfoProcessor.class);
        try {
            String contentFailOver = "localCacheContent" + System.currentTimeMillis();
            //fail over null
            localConfigInfoProcessorMockedStatic.when(
                    () -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant)))
                    .thenReturn(null);
            //snapshot content
            localConfigInfoProcessorMockedStatic.when(
                    () -> LocalConfigInfoProcessor.getSnapshot(any(), eq(dataId), eq(group), eq(tenant)))
                    .thenReturn(contentFailOver);
            //form server error.
            final int timeout = 3000;
            Mockito.when(mockWoker.getServerConfig(dataId, group, tenant, timeout, false))
                    .thenThrow(new NacosException());
            
            final String config = nacosConfigService.getConfig(dataId, group, timeout);
            assertEquals(contentFailOver, config);
        } finally {
            localConfigInfoProcessorMockedStatic.close();
        }
        
    }
    
    @Test
    void testGetConfig403() throws NacosException {
        final String dataId = "1localcache403";
        final String group = "2";
        final String tenant = "public";
        
        MockedStatic<LocalConfigInfoProcessor> localConfigInfoProcessorMockedStatic = Mockito.mockStatic(
                LocalConfigInfoProcessor.class);
        try {
            //fail over null
            localConfigInfoProcessorMockedStatic.when(
                    () -> LocalConfigInfoProcessor.getFailover(any(), eq(dataId), eq(group), eq(tenant)))
                    .thenReturn(null);
            
            //form server error.
            final int timeout = 3000;
            Mockito.when(mockWoker.getServerConfig(dataId, group, tenant, timeout, false))
                    .thenThrow(new NacosException(NacosException.NO_RIGHT, "no right"));
            try {
                nacosConfigService.getConfig(dataId, group, timeout);
                assertTrue(false);
            } catch (NacosException e) {
                assertEquals(NacosException.NO_RIGHT, e.getErrCode());
            }
        } finally {
            localConfigInfoProcessorMockedStatic.close();
        }
    }
    
    @Test
    void testGetConfigAndSignListener() throws NacosException {
        final String dataId = "1";
        final String group = "2";
        final String tenant = "";
        final String content = "123";
        final int timeout = 3000;
        final Listener listener = new Listener() {
            @Override
            public Executor getExecutor() {
                return null;
            }
            
            @Override
            public void receiveConfigInfo(String configInfo) {
            
            }
        };
        
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, "aaa");
        final NacosClientProperties nacosProperties = NacosClientProperties.PROTOTYPE.derive(properties);
        ConfigServerListManager mgr = new ConfigServerListManager(nacosProperties);
        mgr.start();
        ConfigTransportClient client = new ConfigTransportClient(nacosProperties, mgr) {
            @Override
            public void startInternal() throws NacosException {
                // NOOP
            }
            
            @Override
            public String getName() {
                return "TestConfigTransportClient";
            }
            
            @Override
            public void notifyListenConfig() {
                // NOOP
            }
            
            @Override
            public void executeConfigListen() {
                // NOOP
            }
            
            @Override
            public void removeCache(String dataId, String group) {
                // NOOP
            }
            
            @Override
            public ConfigResponse queryConfig(String dataId, String group, String tenant, long readTimeous,
                    boolean notify) throws NacosException {
                ConfigResponse configResponse = new ConfigResponse();
                configResponse.setContent(content);
                configResponse.setDataId(dataId);
                configResponse.setGroup(group);
                configResponse.setTenant(tenant);
                return configResponse;
            }
            
            @Override
            public boolean publishConfig(String dataId, String group, String tenant, String appName, String tag,
                    String betaIps, String content, String encryptedDataKey, String casMd5, String type)
                    throws NacosException {
                return false;
            }
            
            @Override
            public boolean removeConfig(String dataId, String group, String tenant, String tag) throws NacosException {
                return false;
            }
        };
        Mockito.when(mockWoker.getAgent()).thenReturn(client);
        
        final String config = nacosConfigService.getConfigAndSignListener(dataId, group, timeout, listener);
        assertEquals(content, config);
        
        Mockito.verify(mockWoker, Mockito.times(1))
                .addTenantListenersWithContent(dataId, group, content, null, Collections.singletonList(listener));
        assertEquals(content, config);
        
        Mockito.verify(mockWoker, Mockito.times(1))
                .addTenantListenersWithContent(dataId, group, content, null, Arrays.asList(listener));
    }
    
    @Test
    void testAddListener() throws NacosException {
        String dataId = "1";
        String group = "2";
        Listener listener = new Listener() {
            @Override
            public Executor getExecutor() {
                return null;
            }
            
            @Override
            public void receiveConfigInfo(String configInfo) {
            
            }
        };
        
        nacosConfigService.addListener(dataId, group, listener);
        Mockito.verify(mockWoker, Mockito.times(1)).addTenantListeners(dataId, group, Arrays.asList(listener));
    }
    
    @Test
    void testPublishConfig() throws NacosException {
        String dataId = "1";
        String group = "2";
        String content = "123";
        String namespace = "public";
        String type = ConfigType.getDefaultType().getType();
        Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", null, type))
                .thenReturn(true);
        
        final boolean b = nacosConfigService.publishConfig(dataId, group, content);
        assertTrue(b);
        
        Mockito.verify(mockWoker, Mockito.times(1))
                .publishConfig(dataId, group, namespace, null, null, null, content, "", null, type);
    }
    
    @Test
    void testPublishConfig2() throws NacosException {
        String dataId = "1";
        String group = "2";
        String content = "123";
        String namespace = "public";
        String type = ConfigType.PROPERTIES.getType();
        
        Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", null, type))
                .thenReturn(true);
        
        final boolean b = nacosConfigService.publishConfig(dataId, group, content, type);
        assertTrue(b);
        
        Mockito.verify(mockWoker, Mockito.times(1))
                .publishConfig(dataId, group, namespace, null, null, null, content, "", null, type);
    }
    
    @Test
    void testPublishConfigCas() throws NacosException {
        String dataId = "1";
        String group = "2";
        String content = "123";
        String namespace = "public";
        String casMd5 = "96147704e3cb8be8597d55d75d244a02";
        String type = ConfigType.getDefaultType().getType();
        
        Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type))
                .thenReturn(true);
        
        final boolean b = nacosConfigService.publishConfigCas(dataId, group, content, casMd5);
        assertTrue(b);
        
        Mockito.verify(mockWoker, Mockito.times(1))
                .publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type);
    }
    
    @Test
    void testPublishConfigCas2() throws NacosException {
        String dataId = "1";
        String group = "2";
        String content = "123";
        String namespace = "public";
        String casMd5 = "96147704e3cb8be8597d55d75d244a02";
        String type = ConfigType.PROPERTIES.getType();
        
        Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type))
                .thenReturn(true);
        
        final boolean b = nacosConfigService.publishConfigCas(dataId, group, content, casMd5, type);
        assertTrue(b);
        
        Mockito.verify(mockWoker, Mockito.times(1))
                .publishConfig(dataId, group, namespace, null, null, null, content, "", casMd5, type);
    }
    
    @Test
    void testRemoveConfig() throws NacosException {
        String dataId = "1";
        String group = "2";
        String tenant = "public";
        
        Mockito.when(mockWoker.removeConfig(dataId, group, tenant, null)).thenReturn(true);
        
        final boolean b = nacosConfigService.removeConfig(dataId, group);
        assertTrue(b);
        
        Mockito.verify(mockWoker, Mockito.times(1)).removeConfig(dataId, group, tenant, null);
    }
    
    @Test
    void testRemoveListener() {
        String dataId = "1";
        String group = "2";
        Listener listener = new Listener() {
            @Override
            public Executor getExecutor() {
                return null;
            }
            
            @Override
            public void receiveConfigInfo(String configInfo) {
            
            }
        };
        
        nacosConfigService.removeListener(dataId, group, listener);
        Mockito.verify(mockWoker, Mockito.times(1)).removeTenantListener(dataId, group, listener);
    }
    
    @Test
    void testGetServerStatus() {
        Mockito.when(mockWoker.isHealthServer()).thenReturn(true);
        assertEquals("UP", nacosConfigService.getServerStatus());
        Mockito.verify(mockWoker, Mockito.times(1)).isHealthServer();
        
        Mockito.when(mockWoker.isHealthServer()).thenReturn(false);
        assertEquals("DOWN", nacosConfigService.getServerStatus());
        Mockito.verify(mockWoker, Mockito.times(2)).isHealthServer();
        
    }
    
    @Test
    void testFuzzyWatch1() throws NacosException {
        String groupNamePattern = "default_group";
        FuzzyWatchEventWatcher fuzzyWatchEventWatcher = new FuzzyWatchEventWatcher() {
            @Override
            public void onEvent(ConfigFuzzyWatchChangeEvent event) {
            
            }
            
            @Override
            public Executor getExecutor() {
                return null;
            }
        };
        ConfigFuzzyWatchContext context = Mockito.mock(ConfigFuzzyWatchContext.class);
        Mockito.when(mockWoker.addTenantFuzzyWatcher(anyString(), anyString(), any())).thenReturn(context);
        nacosConfigService.fuzzyWatch(groupNamePattern, fuzzyWatchEventWatcher);
        Mockito.verify(mockWoker, Mockito.times(1))
                .addTenantFuzzyWatcher(ALL_PATTERN, groupNamePattern, fuzzyWatchEventWatcher);
        Mockito.verify(context, Mockito.times(1)).createNewFuture();
    }
    
    @Test
    void testFuzzyWatch2() throws NacosException {
        String groupNamePattern = "default_group";
        String dataIdPattern = "dataId*";
        FuzzyWatchEventWatcher fuzzyWatchEventWatcher = new FuzzyWatchEventWatcher() {
            @Override
            public void onEvent(ConfigFuzzyWatchChangeEvent event) {
            
            }
            
            @Override
            public Executor getExecutor() {
                return null;
            }
        };
        ConfigFuzzyWatchContext context = Mockito.mock(ConfigFuzzyWatchContext.class);
        Mockito.when(mockWoker.addTenantFuzzyWatcher(anyString(), anyString(), any())).thenReturn(context);
        nacosConfigService.fuzzyWatch(dataIdPattern, groupNamePattern, fuzzyWatchEventWatcher);
        Mockito.verify(mockWoker, Mockito.times(1))
                .addTenantFuzzyWatcher(dataIdPattern, groupNamePattern, fuzzyWatchEventWatcher);
        Mockito.verify(context, Mockito.times(1)).createNewFuture();
    }
    
    @Test
    void testFuzzyWatch3() throws NacosException {
        String groupNamePattern = "group";
        String dataIdPattern = "dataId*";
        String namespace = "public";
        FuzzyWatchEventWatcher fuzzyWatchEventWatcher = new FuzzyWatchEventWatcher() {
            @Override
            public void onEvent(ConfigFuzzyWatchChangeEvent event) {
            
            }
            
            @Override
            public Executor getExecutor() {
                return null;
            }
        };
        String patternKey = FuzzyGroupKeyPattern.generatePattern(dataIdPattern, groupNamePattern, namespace);
        ConfigFuzzyWatchContext context = new ConfigFuzzyWatchContext("", patternKey);
        Mockito.when(mockWoker.addTenantFuzzyWatcher(anyString(), anyString(), any())).thenReturn(context);
        Future<Set<String>> setFuture = nacosConfigService.fuzzyWatchWithGroupKeys(groupNamePattern,
                fuzzyWatchEventWatcher);
        Mockito.verify(mockWoker, Mockito.times(1))
                .addTenantFuzzyWatcher(ALL_PATTERN, groupNamePattern, fuzzyWatchEventWatcher);
        Assertions.assertNotNull(setFuture);
    }
    
    @Test
    void testFuzzyWatch4() throws NacosException {
        String groupNamePattern = "group";
        String dataIdPattern = "dataId*";
        String namespace = "public";
        FuzzyWatchEventWatcher fuzzyWatchEventWatcher = new FuzzyWatchEventWatcher() {
            @Override
            public void onEvent(ConfigFuzzyWatchChangeEvent event) {
            
            }
            
            @Override
            public Executor getExecutor() {
                return null;
            }
        };
        String patternKey = FuzzyGroupKeyPattern.generatePattern(dataIdPattern, groupNamePattern, namespace);
        ConfigFuzzyWatchContext context = new ConfigFuzzyWatchContext("", patternKey);
        Mockito.when(mockWoker.addTenantFuzzyWatcher(anyString(), anyString(), any())).thenReturn(context);
        Future<Set<String>> setFuture = nacosConfigService.fuzzyWatchWithGroupKeys(dataIdPattern, groupNamePattern,
                fuzzyWatchEventWatcher);
        Mockito.verify(mockWoker, Mockito.times(1))
                .addTenantFuzzyWatcher(dataIdPattern, groupNamePattern, fuzzyWatchEventWatcher);
        Assertions.assertNotNull(setFuture);
    }

    @Test
    void testShutDown() {
        Assertions.assertDoesNotThrow(() -> {
            nacosConfigService.shutDown();
        });
    }
}
