/*
 * 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.core.ability.config;

import com.alibaba.nacos.api.ability.constant.AbilityKey;
import com.alibaba.nacos.api.ability.constant.AbilityStatus;
import com.alibaba.nacos.api.ability.register.AbstractAbilityRegistry;
import com.alibaba.nacos.api.ability.register.impl.ServerAbilities;
import com.alibaba.nacos.common.event.ServerConfigChangeEvent;
import com.alibaba.nacos.core.ability.TestServerAbilityControlManager;
import com.alibaba.nacos.core.ability.control.ServerAbilityControlManager;
import com.alibaba.nacos.sys.env.EnvUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.env.MockEnvironment;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;

/**
 * test for ability in config.
 *
 * @author Daydreamer
 * @date 2022/9/3 12:27
 **/
public class AbilityConfigsTest {
    
    private MockEnvironment environment;
    
    private TestAbilityConfig abilityConfigs;
    
    private ServerAbilityControlManager serverAbilityControlManager;
    
    private Map<AbilityKey, Boolean> currentAbilities;
    
    @BeforeEach
    void setUp() throws Exception {
        environment = new MockEnvironment();
        EnvUtil.setEnvironment(environment);
        abilityConfigs = new TestAbilityConfig();
        inject(abilityConfigs);
        serverAbilityControlManager.enableCurrentNodeAbility(AbilityKey.SERVER_FUZZY_WATCH);
        serverAbilityControlManager.enableCurrentNodeAbility(AbilityKey.SERVER_DISTRIBUTED_LOCK);
    }
    
    void inject(AbilityConfigs abilityConfigs) {
        TestServerAbilityControlManager serverAbilityControlManager = new TestServerAbilityControlManager();
        Map<String, Boolean> newTable = new HashMap<>();
        newTable.put(AbilityKey.SERVER_FUZZY_WATCH.getName(), true);
        newTable.put(AbilityKey.SERVER_DISTRIBUTED_LOCK.getName(), true);
        serverAbilityControlManager.setCurrentSupportingAbility(newTable);
        abilityConfigs.setAbilityHandlerRegistry(serverAbilityControlManager);
        this.serverAbilityControlManager = serverAbilityControlManager;
    }
    
    /**
     * fill field.
     *
     * @throws Exception ignore
     */
    public void fill() throws Exception {
        Field instanceField = ServerAbilities.class.getDeclaredField("INSTANCE");
        Field abilitiesField = AbstractAbilityRegistry.class.getDeclaredField("supportedAbilities");
        abilitiesField.setAccessible(true);
        instanceField.setAccessible(true);
        ServerAbilities serverAbilities = (ServerAbilities) instanceField.get(ServerAbilities.class);
        currentAbilities = (Map<AbilityKey, Boolean>) abilitiesField.get(serverAbilities);
        currentAbilities.put(AbilityKey.SERVER_FUZZY_WATCH, true);
        currentAbilities.put(AbilityKey.SERVER_DISTRIBUTED_LOCK, true);
    }
    
    @Test
    void testLoadAbilities() throws Exception {
        environment.setProperty(AbilityConfigs.PREFIX + AbilityKey.SERVER_FUZZY_WATCH.getName(), Boolean.TRUE.toString());
        environment.setProperty(AbilityConfigs.PREFIX + AbilityKey.SERVER_DISTRIBUTED_LOCK.getName(), Boolean.FALSE.toString());
        // test load
        fill();
        ServerAbilityControlManager manager = new ServerAbilityControlManager();
        // config has higher priority
        assertEquals(AbilityStatus.SUPPORTED, manager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_FUZZY_WATCH));
        assertNotEquals(AbilityStatus.SUPPORTED, manager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_DISTRIBUTED_LOCK));
        // clear
        currentAbilities.clear();
    }
    
    @Test
    void testInit() {
        assertEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_FUZZY_WATCH));
        assertEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_DISTRIBUTED_LOCK));
    }
    
    @Test
    void testConfigChange() throws InterruptedException {
        // test no change
        environment.setProperty(AbilityConfigs.PREFIX + AbilityKey.SERVER_FUZZY_WATCH.getName(), Boolean.TRUE.toString());
        environment.setProperty(AbilityConfigs.PREFIX + AbilityKey.SERVER_DISTRIBUTED_LOCK.getName(), Boolean.TRUE.toString());
        abilityConfigs.onEvent(new ServerConfigChangeEvent());
        assertEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_FUZZY_WATCH));
        assertEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_DISTRIBUTED_LOCK));
        
        // test change
        environment.setProperty(AbilityConfigs.PREFIX + AbilityKey.SERVER_FUZZY_WATCH.getName(), Boolean.FALSE.toString());
        abilityConfigs.onEvent(new ServerConfigChangeEvent());
        assertNotEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_FUZZY_WATCH));
        assertEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_DISTRIBUTED_LOCK));
        
        environment.setProperty(AbilityConfigs.PREFIX + AbilityKey.SERVER_FUZZY_WATCH.getName(), Boolean.TRUE.toString());
        abilityConfigs.onEvent(new ServerConfigChangeEvent());
        assertEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_FUZZY_WATCH));
        
        environment.setProperty(AbilityConfigs.PREFIX + AbilityKey.SERVER_FUZZY_WATCH.getName(), Boolean.FALSE.toString());
        environment.setProperty(AbilityConfigs.PREFIX + AbilityKey.SERVER_DISTRIBUTED_LOCK.getName(), Boolean.FALSE.toString());
        abilityConfigs.onEvent(new ServerConfigChangeEvent());
        assertNotEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_FUZZY_WATCH));
        assertNotEquals(AbilityStatus.SUPPORTED, serverAbilityControlManager.isCurrentNodeAbilityRunning(AbilityKey.SERVER_DISTRIBUTED_LOCK));
    }
    
}
