/*
 * 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.config.server.remote;

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest;
import com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse;
import com.alibaba.nacos.api.remote.request.RequestMeta;
import com.alibaba.nacos.common.utils.MD5Utils;
import com.alibaba.nacos.config.server.model.CacheItem;
import com.alibaba.nacos.config.server.model.ConfigCacheGray;
import com.alibaba.nacos.config.server.model.gray.BetaGrayRule;
import com.alibaba.nacos.config.server.model.gray.ConfigGrayPersistInfo;
import com.alibaba.nacos.config.server.model.gray.GrayRuleManager;
import com.alibaba.nacos.config.server.model.gray.TagGrayRule;
import com.alibaba.nacos.config.server.service.ConfigCacheService;
import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory;
import com.alibaba.nacos.config.server.service.dump.disk.ConfigRocksDbDiskService;
import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService;
import com.alibaba.nacos.config.server.utils.GroupKey2;
import com.alibaba.nacos.config.server.utils.PropertyUtil;
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.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.env.StandardEnvironment;

import java.io.IOException;

import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG;
import static com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse.CONFIG_NOT_FOUND;
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.eq;
import static org.mockito.Mockito.when;

// todo open the test case
@ExtendWith(MockitoExtension.class)
class ConfigQueryRequestHandlerTest {
    
    static MockedStatic<ConfigCacheService> configCacheServiceMockedStatic;
    
    static MockedStatic<PropertyUtil> propertyUtilMockedStatic;
    
    static MockedStatic<ConfigDiskServiceFactory> configDiskServiceFactoryMockedStatic;
    
    String dataId = "dataId" + System.currentTimeMillis();
    
    String group = "group" + System.currentTimeMillis();
    
    String tenant = "tenant" + System.currentTimeMillis();
    
    String content = "content" + System.currentTimeMillis();
    
    private ConfigQueryRequestHandler configQueryRequestHandler;
    
    @AfterEach
    void after() {
        configCacheServiceMockedStatic.close();
        propertyUtilMockedStatic.close();
        configDiskServiceFactoryMockedStatic.close();
        EnvUtil.setEnvironment(null);
    }
    
    @BeforeEach
    void setUp() throws IOException {
        EnvUtil.setEnvironment(new StandardEnvironment());
        configCacheServiceMockedStatic = Mockito.mockStatic(ConfigCacheService.class);
        propertyUtilMockedStatic = Mockito.mockStatic(PropertyUtil.class);
        configDiskServiceFactoryMockedStatic = Mockito.mockStatic(ConfigDiskServiceFactory.class);
        configQueryRequestHandler = new ConfigQueryRequestHandler(new ConfigQueryChainService());
        final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID);
        when(ConfigCacheService.tryConfigReadLock(groupKey)).thenReturn(1);
        propertyUtilMockedStatic.when(PropertyUtil::getMaxContent).thenReturn(1024 * 1000);
    }
    
    /**
     * get normal config from local disk.
     *
     * @throws Exception Exception.
     */
    @Test
    void testGetNormal() throws Exception {
        
        final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID);
        String content = "content_from_notdirectreadÄãºÃ" + System.currentTimeMillis();
        ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class);
        when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService);
        
        CacheItem cacheItem = new CacheItem(groupKey);
        cacheItem.getConfigCache().setMd5(MD5Utils.md5Hex(content, "UTF-8"));
        cacheItem.getConfigCache().setEncryptedDataKey("key_testGetNormal_NotDirectRead");
        when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem);
        
        ConfigQueryRequest configQueryRequest = new ConfigQueryRequest();
        configQueryRequest.setDataId(dataId);
        configQueryRequest.setGroup(group);
        RequestMeta requestMeta = new RequestMeta();
        requestMeta.setClientIp("127.0.0.1");
        
        when(configRocksDbDiskService.getContent(eq(dataId), eq(group), eq(Constants.DEFAULT_NAMESPACE_ID))).thenReturn(
                content);
        ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta);
        assertEquals(content, response.getContent());
        assertEquals(MD5Utils.md5Hex(content, "UTF-8"), response.getMd5());
        assertEquals("key_testGetNormal_NotDirectRead", response.getEncryptedDataKey());
        
        assertFalse(response.isBeta());
        assertNull(response.getTag());
        
        assertEquals(content, response.getContent());
    }
    
    
    /**
     * get beta config from local disk.
     *
     * @throws Exception Exception.
     */
    @Test
    void testGetBeta() throws Exception {
        
        final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID);
        ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class);
        when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService);
        
        CacheItem cacheItem = new CacheItem(groupKey);
        cacheItem.initConfigGrayIfEmpty(BetaGrayRule.TYPE_BETA);
        String content = "content_from_beta_notdirectreadÄãºÃ" + System.currentTimeMillis();
        ConfigCacheGray configCacheGrayBeta = cacheItem.getConfigCacheGray().get(BetaGrayRule.TYPE_BETA);
        configCacheGrayBeta.setMd5(MD5Utils.md5Hex(content, "UTF-8"));
        configCacheGrayBeta.setEncryptedDataKey("key_testGetBeta_NotDirectRead");
        ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(BetaGrayRule.TYPE_BETA,
                BetaGrayRule.VERSION, "127.0.0.1", -1000);
        configCacheGrayBeta.resetGrayRule(GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo));
        cacheItem.sortConfigGray();
        when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem);
        
        ConfigQueryRequest configQueryRequest = new ConfigQueryRequest();
        configQueryRequest.setDataId(dataId);
        configQueryRequest.setGroup(group);
        RequestMeta requestMeta = new RequestMeta();
        requestMeta.setClientIp("127.0.0.1");
        
        when(configRocksDbDiskService.getGrayContent(eq(dataId), eq(group), eq(Constants.DEFAULT_NAMESPACE_ID),
                eq(BetaGrayRule.TYPE_BETA))).thenReturn(content);
        ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta);
        //check content&md5
        assertEquals(content, response.getContent());
        assertEquals(MD5Utils.md5Hex(content, "UTF-8"), response.getMd5());
        //check flags.
        assertTrue(response.isBeta());
        assertNull(response.getTag());
        
    }
    
    /**
     * get tag config ,but not found.
     *
     * @throws Exception Exception.
     */
    @Test
    void testGetTagNotFound() throws Exception {
        
        final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID);
        String content = "content_from_tag_withtagÄãºÃ" + System.currentTimeMillis();
        ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class);
        when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService);
        
        CacheItem cacheItem = new CacheItem(groupKey);
        cacheItem.getConfigCache().setMd5(MD5Utils.md5Hex(content, "UTF-8"));
        cacheItem.getConfigCache().setEncryptedDataKey("key_testGetTag_NotFound");
        
        when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem);
        
        ConfigQueryRequest configQueryRequest = new ConfigQueryRequest();
        configQueryRequest.setDataId(dataId);
        configQueryRequest.setGroup(group);
        String specificTag = "specific_tag";
        configQueryRequest.setTag(specificTag);
        String autoTag = "auto_tag111";
        configQueryRequest.putHeader(VIPSERVER_TAG, autoTag);
        RequestMeta requestMeta = new RequestMeta();
        requestMeta.setClientIp("127.0.0.1");
        
        ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta);
        
        //check content&md5
        assertNull(response.getContent());
        assertNull(response.getMd5());
        assertEquals(CONFIG_NOT_FOUND, response.getErrorCode());
        assertNull(response.getEncryptedDataKey());
        
        //check flags.
        assertFalse(response.isBeta());
        assertEquals(response.getTag(), specificTag);
        
    }
    
    /**
     * get tag config from local disk.
     *
     * @throws Exception Exception.
     */
    @Test
    void testGetTagWithTag() throws Exception {
        
        final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID);
        String content = "content_from_tag_notdirectreadÄãºÃ" + System.currentTimeMillis();
        ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class);
        when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService);
        
        CacheItem cacheItem = new CacheItem(groupKey);
        cacheItem.getConfigCache().setMd5(MD5Utils.md5Hex(content, "UTF-8"));
        cacheItem.getConfigCache().setEncryptedDataKey("key_formal");
        
        String specificTag = "specific_tag";
        cacheItem.initConfigGrayIfEmpty(TagGrayRule.TYPE_TAG + "_" + specificTag);
        ConfigCacheGray configCacheGrayTag = cacheItem.getConfigCacheGray()
                .get(TagGrayRule.TYPE_TAG + "_" + specificTag);
        String tagContent = "content_from_specific_tag_directreadÄãºÃ" + System.currentTimeMillis();
        configCacheGrayTag.setMd5(MD5Utils.md5Hex(tagContent, "UTF-8"));
        configCacheGrayTag.setEncryptedDataKey("key_testGetTag_NotDirectRead");
        ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(TagGrayRule.TYPE_TAG,
                TagGrayRule.VERSION, specificTag, -999);
        configCacheGrayTag.resetGrayRule(GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo));
        cacheItem.sortConfigGray();
        //specific tag to get
        when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem);
        
        ConfigQueryRequest configQueryRequest = new ConfigQueryRequest();
        configQueryRequest.setDataId(dataId);
        configQueryRequest.setGroup(group);
        configQueryRequest.setTag(specificTag);
        String autoTag = "auto_tag";
        configQueryRequest.putHeader(VIPSERVER_TAG, autoTag);
        RequestMeta requestMeta = new RequestMeta();
        requestMeta.setClientIp("127.0.0.1");
        
        //mock disk read.
        when(configRocksDbDiskService.getGrayContent(eq(dataId), eq(group), eq(Constants.DEFAULT_NAMESPACE_ID),
                eq(TagGrayRule.TYPE_TAG + "_" + specificTag))).thenReturn(tagContent);
        ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta);
        
        //check content&md5
        assertEquals(tagContent, response.getContent());
        assertEquals(MD5Utils.md5Hex(tagContent, "UTF-8"), response.getMd5());
        assertEquals("key_testGetTag_NotDirectRead", response.getEncryptedDataKey());
        //check flags.
        assertFalse(response.isBeta());
        assertEquals(response.getTag(), specificTag);
        
    }
    
    /**
     * get tao config of auto tag matchd from local disk.
     *
     * @throws Exception Exception.
     */
    @Test
    void testGetTagAutoTag() throws Exception {
        
        final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID);
        String content = "content_from_tag_notdirectreadÄãºÃ" + System.currentTimeMillis();
        ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class);
        when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService);
        
        String autoTag = "auto_tag";
        CacheItem cacheItem = new CacheItem(groupKey);
        cacheItem.initConfigGrayIfEmpty(TagGrayRule.TYPE_TAG + "_" + autoTag);
        cacheItem.getConfigCache().setMd5(MD5Utils.md5Hex(content, "UTF-8"));
        ConfigCacheGray configCacheGrayTag = cacheItem.getConfigCacheGray().get(TagGrayRule.TYPE_TAG + "_" + autoTag);
        String tagContent = "content_from_specific_tag_directreadÄãºÃ" + System.currentTimeMillis();
        configCacheGrayTag.setMd5(MD5Utils.md5Hex(tagContent, "UTF-8"));
        configCacheGrayTag.setEncryptedDataKey("key_testGetTag_AutoTag_NotDirectRead");
        ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(TagGrayRule.TYPE_TAG,
                TagGrayRule.VERSION, autoTag, -999);
        configCacheGrayTag.resetGrayRule(GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo));
        cacheItem.sortConfigGray();
        when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem);
        
        ConfigQueryRequest configQueryRequest = new ConfigQueryRequest();
        configQueryRequest.setDataId(dataId);
        configQueryRequest.setGroup(group);
        RequestMeta requestMeta = new RequestMeta();
        requestMeta.setClientIp("127.0.0.1");
        requestMeta.getAppLabels().put(VIPSERVER_TAG, autoTag);
        //mock disk read.
        when(configRocksDbDiskService.getGrayContent(eq(dataId), eq(group), eq(Constants.DEFAULT_NAMESPACE_ID),
                eq(TagGrayRule.TYPE_TAG + "_" + autoTag))).thenReturn(tagContent);
        ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta);
        
        //check content&md5
        assertEquals(tagContent, response.getContent());
        assertEquals(MD5Utils.md5Hex(tagContent, "UTF-8"), response.getMd5());
        assertEquals("key_testGetTag_AutoTag_NotDirectRead", response.getEncryptedDataKey());
        
        //check flags.
        assertFalse(response.isBeta());
        assertEquals(response.getTag(), autoTag);
        
    }
    
    /**
     * get normal config from local disk.
     *
     * @throws Exception Exception.
     */
    @Test
    void testGetConfigNotExistAndConflict() throws Exception {
        
        String dataId = "dataId" + System.currentTimeMillis();
        String group = "group" + System.currentTimeMillis();
        String tenant = "tenant" + System.currentTimeMillis();
        //test config not exist
        configCacheServiceMockedStatic.when(
                () -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))).thenReturn(0);
        final String groupKey = GroupKey2.getKey(dataId, group, tenant);
        when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(null);
        
        ConfigQueryRequest configQueryRequest = new ConfigQueryRequest();
        configQueryRequest.setDataId(dataId);
        configQueryRequest.setGroup(group);
        configQueryRequest.setTenant(tenant);
        RequestMeta requestMeta = new RequestMeta();
        requestMeta.setClientIp("127.0.0.1");
        
        ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta);
        assertEquals(CONFIG_NOT_FOUND, response.getErrorCode());
        assertNull(response.getContent());
        assertNull(response.getMd5());
        assertFalse(response.isBeta());
        assertNull(response.getTag());
        
        //test config conflict
        when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(new CacheItem(groupKey));
        configCacheServiceMockedStatic.when(
                () -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))).thenReturn(-1);
        ConfigQueryResponse responseConflict = configQueryRequestHandler.handle(configQueryRequest, requestMeta);
        assertEquals(ConfigQueryResponse.CONFIG_QUERY_CONFLICT, responseConflict.getErrorCode());
        assertNull(responseConflict.getContent());
        assertNull(responseConflict.getMd5());
        assertFalse(responseConflict.isBeta());
        assertNull(responseConflict.getTag());
        
    }
    
}
