/*
 * Copyright 1999-2022 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.config.server.service;

import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
import com.alibaba.nacos.common.http.HttpRestResult;
import com.alibaba.nacos.common.http.client.NacosAsyncRestTemplate;
import com.alibaba.nacos.common.http.client.NacosRestTemplate;
import com.alibaba.nacos.common.http.param.Header;
import com.alibaba.nacos.common.http.param.Query;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.config.server.model.ListenerCheckResult;
import com.alibaba.nacos.config.server.model.SampleResult;
import com.alibaba.nacos.config.server.service.notify.HttpClientManager;
import com.alibaba.nacos.core.cluster.Member;
import com.alibaba.nacos.core.cluster.ServerMemberManager;
import com.alibaba.nacos.sys.env.EnvUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;

@ExtendWith(SpringExtension.class)
class ConfigSubServiceTest {
    
    @Mock
    ServerMemberManager serverMemberManager;
    
    MockedStatic<EnvUtil> envUtilMockedStatic;
    
    MockedStatic<HttpClientManager> httpClientManagerMockedStatic;
    
    @Mock
    NacosRestTemplate nacosRestTemplate;
    
    @Mock
    NacosAsyncRestTemplate nacosAsyncRestTemplate;
    
    private ConfigSubService configSubService;
    
    @BeforeEach
    void startUP() {
        
        httpClientManagerMockedStatic = Mockito.mockStatic(HttpClientManager.class);
        envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class);
        configSubService = new ConfigSubService(serverMemberManager);
        envUtilMockedStatic.when(() -> EnvUtil.getContextPath()).thenReturn("/nacos");
        envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), anyString())).thenReturn("mock string");
        EnvUtil.setContextPath("/nacos");
        httpClientManagerMockedStatic.when(() -> HttpClientManager.getNacosRestTemplate())
                .thenReturn(nacosRestTemplate);
        httpClientManagerMockedStatic.when(() -> HttpClientManager.getNacosAsyncRestTemplate())
                .thenReturn(nacosAsyncRestTemplate);
    }
    
    @AfterEach
    void after() {
        if (!envUtilMockedStatic.isClosed()) {
            envUtilMockedStatic.close();
        }
        httpClientManagerMockedStatic.close();
    }
    
    @Test
    void testGetCollectSampleResult() throws Exception {
        envUtilMockedStatic.close();
        EnvUtil.setContextPath("/nacos");
        ConfigurableEnvironment environment = Mockito.mock(ConfigurableEnvironment.class);
        EnvUtil.setEnvironment(environment);
        Mockito.when(environment.getProperty(anyString(), anyString())).thenReturn("/nacos");
        
        Map<String, Member> mockedMembers = new HashMap<>();
        mockedMembers.put("127.0.0.1", createMember("127.0.0.1"));
        mockedMembers.put("127.0.0.2", createMember("127.0.0.2"));
        mockedMembers.put("127.0.0.3", createMember("127.0.0.3"));
        //mock server member
        Mockito.when(serverMemberManager.allMembers()).thenReturn(mockedMembers.values());
        Mockito.when(serverMemberManager.getServerList()).thenReturn(mockedMembers);
        String dataId = "dataid1234";
        String group = "group34567";
        String tenant = "tenant456789";
        int sampleTimes = 3;
        //cant mock static method cross thread,so cant verify return obj here.
        configSubService.getCollectSampleResult(dataId, group, tenant, sampleTimes);
        configSubService.getCollectSampleResultByIp("127.0.0.1", 3);
    }
    
    @Test
    void testRunSingleJob() throws Exception {
        Map<String, String> params = new HashMap<>();
        params.put("dataId", "d1");
        params.put("group", "g1");
        params.put("tenant", "t1");
        
        HttpRestResult<Object> httpRestResult = new HttpRestResult<>();
        httpRestResult.setCode(200);
        httpRestResult.setMessage("success");
        
        SampleResult sampleResult1 = new SampleResult();
        Map<String, String> listener1 = new HashMap<>();
        listener1.put("config1", "md51123");
        listener1.put("config11", "md5123123");
        sampleResult1.setLisentersGroupkeyStatus(listener1);
        String mockJsonString = JacksonUtils.toJson(sampleResult1);
        httpRestResult.setData(mockJsonString);
        //mock success
        Mockito.when(nacosRestTemplate.get(anyString(), any(Header.class), eq(Query.EMPTY), eq(String.class)))
                .thenReturn(httpRestResult);
        String url = "url";
        SampleResult returnObj = (SampleResult) ConfigSubService.runSingleJob("127.0.0.1", params, url,
                SampleResult.class);
        assertEquals(sampleResult1.getLisentersGroupkeyStatus(), returnObj.getLisentersGroupkeyStatus());
        //mock fail response
        httpRestResult.setCode(500);
        Mockito.when(nacosRestTemplate.get(anyString(), any(Header.class), eq(Query.EMPTY), eq(String.class)))
                .thenReturn(httpRestResult);
        SampleResult returnObj500 = (SampleResult) ConfigSubService.runSingleJob("127.0.0.1", params, url,
                SampleResult.class);
        assertNull(returnObj500);
        
        //mock get url throw exception
        Mockito.when(nacosRestTemplate.get(anyString(), any(Header.class), eq(Query.EMPTY), eq(String.class)))
                .thenThrow(new NacosRuntimeException(500, "timeout"));
        SampleResult returnObjTimeout = (SampleResult) ConfigSubService.runSingleJob("127.0.0.1", params, url,
                SampleResult.class);
        assertNull(returnObjTimeout);
        
    }
    
    @Test
    void testClusterListenerJob() throws Exception {
        Map<String, Member> mockedMembers = new HashMap<>();
        mockedMembers.put("127.0.0.1", createMember("127.0.0.1"));
        mockedMembers.put("127.0.0.2", createMember("127.0.0.2"));
        mockedMembers.put("127.0.0.3", createMember("127.0.0.3"));
        //mock server member
        Mockito.when(serverMemberManager.allMembers()).thenReturn(mockedMembers.values());
        Mockito.when(serverMemberManager.getServerList()).thenReturn(mockedMembers);
        
        CompletionService mockService = Mockito.mock(CompletionService.class);
        //mock all success
        Mockito.when(mockService.poll(anyLong(), any(TimeUnit.class)))
                .thenReturn(createSampleResultFuture(true, true), createSampleResultFuture(true, true),
                        createSampleResultFuture(true, true));
        Map<String, String> params = new HashMap<>();
        ConfigSubService.ClusterListenerJob clusterListenerJob = new ConfigSubService.ClusterListenerJob(params,
                mockService, serverMemberManager);
        List<SampleResult> sampleResults = clusterListenerJob.runJobs();
        assertEquals(3, sampleResults.size());
        
        //mock success with exception
        Mockito.when(mockService.poll(anyLong(), any(TimeUnit.class)))
                .thenReturn(createSampleResultFuture(true, true), createSampleResultFuture(false, false))
                .thenThrow(new NacosRuntimeException(500, "13"));
        Map<String, String> params2 = new HashMap<>();
        ConfigSubService.ClusterListenerJob clusterListenerJob2 = new ConfigSubService.ClusterListenerJob(params2,
                mockService, serverMemberManager);
        List<SampleResult> sampleResults2 = clusterListenerJob2.runJobs();
        assertEquals(1, sampleResults2.size());
        assertFalse(sampleResults2.get(0).getLisentersGroupkeyStatus().isEmpty());
        
    }
    
    @Test
    void testMergeSampleResult() throws Exception {
        SampleResult sampleResult1 = new SampleResult();
        Map<String, String> listener1 = new HashMap<>();
        listener1.put("config1", "md51123");
        listener1.put("config11", "md5123123");
        sampleResult1.setLisentersGroupkeyStatus(listener1);
        SampleResult sampleResult2 = new SampleResult();
        Map<String, String> listener2 = new HashMap<>();
        listener2.put("config22", "md51123");
        listener2.put("config2", "md5123123");
        sampleResult2.setLisentersGroupkeyStatus(listener2);
        List<SampleResult> sampleResults = new ArrayList<>();
        sampleResults.add(sampleResult2);
        SampleResult sampleResult3 = new SampleResult();
        Map<String, String> listener3 = new HashMap<>();
        listener3.put("config33", "md51123");
        listener3.put("config3", "md5123123");
        sampleResult3.setLisentersGroupkeyStatus(listener3);
        sampleResults.add(sampleResult3);
        //sampleResult ips is null
        SampleResult sampleResultMerge1 = configSubService.mergeSampleResult(sampleResult1, sampleResults);
        assertEquals(6, sampleResultMerge1.getLisentersGroupkeyStatus().size());
        
        SampleResult sampleResultMerge2 = configSubService.mergeSampleResult(new SampleResult(), sampleResults);
        assertEquals(4, sampleResultMerge2.getLisentersGroupkeyStatus().size());
    }
    
    @Test
    void testMergeListenerCheckResult() throws Exception {
        ListenerCheckResult sampleResult2 = new ListenerCheckResult();
        sampleResult2.setHasListener(true);
        sampleResult2.setCode(200);
        List<ListenerCheckResult> sampleResults = new ArrayList<ListenerCheckResult>();
        sampleResults.add(sampleResult2);
        ListenerCheckResult sampleResult3 = new ListenerCheckResult();
        sampleResult3.setHasListener(false);
        sampleResult3.setCode(200);
        sampleResults.add(sampleResult3);
        ListenerCheckResult sampleResult1 = new ListenerCheckResult();
        //one ip return true
        ListenerCheckResult sampleResultMerge1 = configSubService.mergeListenerCheckResult(sampleResult1, sampleResults,
                2);
        assertEquals(200, sampleResultMerge1.getCode());
        assertTrue(sampleResultMerge1.isHasListener());
        //all ip return false,but not equals member size
        sampleResult2.setHasListener(false);
        sampleResult3.setHasListener(false);
        sampleResult1.setHasListener(false);
        ListenerCheckResult sampleResultMerge2 = configSubService.mergeListenerCheckResult(sampleResult1, sampleResults,
                3);
        assertEquals(201, sampleResultMerge2.getCode());
        assertFalse(sampleResultMerge2.isHasListener());
        
    }
    
    private Future<SampleResult> createSampleResultFuture(boolean success, boolean lisentersGroupkeyStatus) {
        Future<SampleResult> future = new Future<SampleResult>() {
            
            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
                return false;
            }
            
            @Override
            public boolean isCancelled() {
                return false;
            }
            
            @Override
            public boolean isDone() {
                return success ? true : false;
            }
            
            @Override
            public SampleResult get() {
                
                return success ? createSampleResult() : null;
            }
            
            @Override
            public SampleResult get(long timeout, TimeUnit unit) {
                return success ? createSampleResult() : null;
            }
            
            SampleResult createSampleResult() {
                SampleResult sampleResult = new SampleResult();
                if (lisentersGroupkeyStatus) {
                    Map<String, String> listener = new HashMap<>();
                    listener.put("config1", "md51123");
                    listener.put("config2", "md5123123");
                    sampleResult.setLisentersGroupkeyStatus(listener);
                }
                return sampleResult;
            }
        };
        return future;
    }
    
    Member createMember(String ip) {
        Member member = new Member();
        member.setIp(ip);
        member.setPort(8848);
        return member;
    }
}
