/*
 * 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.service.dump;

import com.alibaba.nacos.config.server.manager.TaskManager;
import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent;
import com.alibaba.nacos.config.server.service.ConfigMigrateService;
import com.alibaba.nacos.config.server.service.dump.task.DumpTask;
import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService;
import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService;
import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService;
import com.alibaba.nacos.config.server.utils.ConfigExecutor;
import com.alibaba.nacos.config.server.utils.GroupKey;
import com.alibaba.nacos.config.server.utils.PropertyUtil;
import com.alibaba.nacos.core.cluster.ServerMemberManager;
import com.alibaba.nacos.core.namespace.repository.NamespacePersistService;
import com.alibaba.nacos.persistence.datasource.DataSourceService;
import com.alibaba.nacos.persistence.datasource.DynamicDataSource;
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.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.concurrent.TimeUnit;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;

@ExtendWith(SpringExtension.class)
class DumpServiceTest {
    
    private static final String BETA_TABLE_NAME = "config_info_beta";
    
    private static final String TAG_TABLE_NAME = "config_info_tag";
    
    @Mock
    DefaultHistoryConfigCleaner defaultHistoryConfigCleaner = new DefaultHistoryConfigCleaner();
    
    @Mock
    ConfigInfoPersistService configInfoPersistService;
    
    @Mock
    
    NamespacePersistService namespacePersistService;
    
    @Mock
    HistoryConfigInfoPersistService historyConfigInfoPersistService;
    
    @Mock
    ConfigInfoGrayPersistService configInfoGrayPersistService;
    
    @Mock
    ServerMemberManager memberManager;
    
    @Mock
    ConfigMigrateService configMigrateService;
    
    MockedStatic<EnvUtil> envUtilMockedStatic;
    
    MockedStatic<ConfigExecutor> configExecutorMocked;
    
    MockedStatic<PropertyUtil> propertyUtilMockedStatic;
    
    MockedStatic<HistoryConfigCleanerManager> historyConfigCleanerManagerMockedStatic;
    
    @Mock
    private DataSourceService dataSourceService;
    
    private DumpService dumpService;
    
    @Mock
    private TaskManager dumpTaskMgr;
    
    @BeforeEach
    void setUp() {
        envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class);
        propertyUtilMockedStatic = Mockito.mockStatic(PropertyUtil.class);
        propertyUtilMockedStatic.when(() -> PropertyUtil.getAllDumpPageSize()).thenReturn(100);
        propertyUtilMockedStatic.when(() -> PropertyUtil.getDumpChangeWorkerInterval()).thenReturn(1000 * 60L);
        
        ReflectionTestUtils.setField(DynamicDataSource.getInstance(), "localDataSourceService", dataSourceService);
        ReflectionTestUtils.setField(DynamicDataSource.getInstance(), "basicDataSourceService", dataSourceService);
        dumpService = new ExternalDumpService(configInfoPersistService, namespacePersistService,
                historyConfigInfoPersistService, configInfoGrayPersistService, memberManager, configMigrateService);
        configExecutorMocked = Mockito.mockStatic(ConfigExecutor.class);
        historyConfigCleanerManagerMockedStatic = Mockito.mockStatic(HistoryConfigCleanerManager.class);
        historyConfigCleanerManagerMockedStatic.when(
                () -> HistoryConfigCleanerManager.getHistoryConfigCleaner(anyString()))
                .thenReturn(defaultHistoryConfigCleaner);
        
    }
    
    @AfterEach
    void after() {
        envUtilMockedStatic.close();
        configExecutorMocked.close();
        propertyUtilMockedStatic.close();
        historyConfigCleanerManagerMockedStatic.close();
    }
    
    @Test
    void dumpRequest() throws Throwable {
        String dataId = "12345667dataId";
        String group = "234445group";
        DumpRequest dumpRequest = DumpRequest.create(dataId, group, "testtenant", System.currentTimeMillis(),
                "127.0.0.1");
        // TaskManager dumpTaskMgr;
        ReflectionTestUtils.setField(dumpService, "dumpTaskMgr", dumpTaskMgr);
        Mockito.doNothing().when(dumpTaskMgr).addTask(any(), any());
        dumpService.dump(dumpRequest);
        Mockito.verify(dumpTaskMgr, times(1))
                .addTask(eq(GroupKey.getKeyTenant(dataId, group, dumpRequest.getTenant())), any(DumpTask.class));
        
        dumpRequest.setGrayName("tag_123");
        dumpService.dump(dumpRequest);
        Mockito.verify(dumpTaskMgr, times(1)).addTask(
                eq(GroupKey.getKeyTenant(dataId, group, dumpRequest.getTenant()) + "+gray+"
                        + dumpRequest.getGrayName()), any(DumpTask.class));
        
    }
    
    @Test
    void dumpOperate() throws Throwable {
        configExecutorMocked.when(
                () -> ConfigExecutor.scheduleConfigTask(any(Runnable.class), anyInt(), anyInt(), any(TimeUnit.class)))
                .thenAnswer(invocation -> null);
        configExecutorMocked.when(
                () -> ConfigExecutor.scheduleConfigChangeTask(any(Runnable.class), anyInt(), any(TimeUnit.class)))
                .thenAnswer(invocation -> null);
        Mockito.when(namespacePersistService.isExistTable(BETA_TABLE_NAME)).thenReturn(true);
        Mockito.when(namespacePersistService.isExistTable(TAG_TABLE_NAME)).thenReturn(true);
        
        Mockito.when(configInfoPersistService.findConfigMaxId()).thenReturn(300L);
        dumpService.dumpOperate();
        
        // expect dump
        Mockito.verify(configInfoPersistService, times(1)).findAllConfigInfoFragment(0, 100, true);
        Mockito.verify(configInfoPersistService, times(1)).findConfigMaxId();
        Mockito.verify(configInfoGrayPersistService, times(1)).configInfoGrayCount();
        
        // expect dump formal,beta,tag,history clear,config change task to be scheduled.
        // expect config clear history task be scheduled.
        configExecutorMocked.verify(
                () -> ConfigExecutor.scheduleConfigTask(any(DumpService.DumpAllProcessorRunner.class), anyLong(),
                        anyLong(), eq(TimeUnit.MINUTES)), times(1));
        
        configExecutorMocked.verify(
                () -> ConfigExecutor.scheduleConfigTask(any(DumpService.DumpAllGrayProcessorRunner.class), anyLong(),
                        anyLong(), eq(TimeUnit.MINUTES)), times(1));
        configExecutorMocked.verify(
                () -> ConfigExecutor.scheduleConfigChangeTask(any(DumpChangeConfigWorker.class), anyLong(),
                        eq(TimeUnit.MILLISECONDS)), times(1));
        configExecutorMocked.verify(
                () -> ConfigExecutor.scheduleConfigTask(any(DumpService.ConfigHistoryClear.class), anyLong(), anyLong(),
                        eq(TimeUnit.MINUTES)), times(1));
    }
    
    @Test
    void clearHistory() {
        envUtilMockedStatic.when(() -> EnvUtil.getProperty(eq("nacos.config.retention.days"))).thenReturn("10");
        Mockito.when(memberManager.isFirstIp()).thenReturn(true);
        DumpService.ConfigHistoryClear configHistoryClear = dumpService.new ConfigHistoryClear(
                defaultHistoryConfigCleaner);
        configHistoryClear.run();
        Mockito.verify(defaultHistoryConfigCleaner, times(1)).cleanHistoryConfig();
    }
    
    @Test
    void testHandleConfigDataChange() {
        ConfigDataChangeEvent configDataChangeEvent = new ConfigDataChangeEvent("dataId", "group", null,
                System.currentTimeMillis());
        ReflectionTestUtils.setField(dumpService, "dumpTaskMgr", dumpTaskMgr);
        Mockito.doNothing().when(dumpTaskMgr).addTask(any(), any());
        
        dumpService.handleConfigDataChange(configDataChangeEvent);
        Mockito.verify(dumpTaskMgr, times(1)).addTask(
                eq(GroupKey.getKeyTenant(configDataChangeEvent.dataId, configDataChangeEvent.group,
                        configDataChangeEvent.tenant)), any(DumpTask.class));
    }
    
}
