/*
 * 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.naming.core.v2.metadata;

import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.naming.core.v2.client.Client;
import com.alibaba.nacos.naming.core.v2.event.client.ClientEvent;
import com.alibaba.nacos.naming.core.v2.event.metadata.MetadataEvent;
import com.alibaba.nacos.naming.core.v2.pojo.Service;
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.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith(MockitoExtension.class)
class NamingMetadataManagerTest {
    
    private static final String METADATA_ID = "METADATA_ID";
    
    @Mock
    private Service service;
    
    @Mock
    private ServiceMetadata serviceMetadata;
    
    @Mock
    private InstanceMetadata instanceMetadata;
    
    @Mock
    private MetadataEvent.InstanceMetadataEvent instanceMetadataEvent;
    
    @Mock
    private MetadataEvent.ServiceMetadataEvent serviceMetadataEvent;
    
    @Mock
    private ClientEvent.ClientDisconnectEvent clientDisconnectEvent;
    
    @Mock
    private Client client;
    
    private NamingMetadataManager namingMetadataManager;
    
    @BeforeEach
    void setUp() throws Exception {
        namingMetadataManager = new NamingMetadataManager();
        
        Class<NamingMetadataManager> namingMetadataManagerClass = NamingMetadataManager.class;
        Field serviceMetadataMapField = namingMetadataManagerClass.getDeclaredField("serviceMetadataMap");
        serviceMetadataMapField.setAccessible(true);
        ConcurrentMap<Service, ServiceMetadata> serviceMetadataMap = (ConcurrentMap<Service, ServiceMetadata>) serviceMetadataMapField.get(
                namingMetadataManager);
        serviceMetadataMap.put(service, serviceMetadata);
        
        Field instanceMetadataMapField = namingMetadataManagerClass.getDeclaredField("instanceMetadataMap");
        instanceMetadataMapField.setAccessible(true);
        
        ConcurrentMap<String, InstanceMetadata> concurrentMap = new ConcurrentHashMap<>();
        concurrentMap.put(METADATA_ID, instanceMetadata);
        ((ConcurrentMap<Service, ConcurrentMap<String, InstanceMetadata>>) instanceMetadataMapField.get(namingMetadataManager)).put(service,
                concurrentMap);
    }
    
    @Test
    void testContainServiceMetadata() {
        boolean result = namingMetadataManager.containServiceMetadata(service);
        
        assertTrue(result);
    }
    
    @Test
    void testContainInstanceMetadata() {
        boolean result = namingMetadataManager.containInstanceMetadata(service, METADATA_ID);
        
        assertTrue(result);
    }
    
    @Test
    void testGetServiceMetadata() {
        Optional<ServiceMetadata> serviceMetadata = namingMetadataManager.getServiceMetadata(service);
        
        assertTrue(serviceMetadata.isPresent());
        assertNotNull(serviceMetadata.get());
    }
    
    @Test
    void testGetInstanceMetadata() {
        Optional<InstanceMetadata> instanceMetadata = namingMetadataManager.getInstanceMetadata(service, METADATA_ID);
        
        assertTrue(instanceMetadata.isPresent());
        assertNotNull(instanceMetadata.get());
    }
    
    @Test
    void testUpdateServiceMetadata() throws NoSuchFieldException, IllegalAccessException {
        
        ServiceMetadata serviceMetadata = new ServiceMetadata();
        Class<ServiceMetadata> serviceMetadataClass = ServiceMetadata.class;
        Field ephemeral = serviceMetadataClass.getDeclaredField("ephemeral");
        ephemeral.setAccessible(true);
        ephemeral.set(serviceMetadata, false);
        
        namingMetadataManager.updateServiceMetadata(service, serviceMetadata);
        
        Optional<ServiceMetadata> optional = namingMetadataManager.getServiceMetadata(service);
        assertTrue(optional.isPresent());
        assertNotNull(optional.get());
        assertFalse(optional.get().isEphemeral());
    }
    
    @Test
    void testUpdateInstanceMetadata() throws NoSuchFieldException, IllegalAccessException {
        InstanceMetadata instanceMetadata = new InstanceMetadata();
        Class<InstanceMetadata> instanceMetadataClass = InstanceMetadata.class;
        Field enabled = instanceMetadataClass.getDeclaredField("enabled");
        enabled.setAccessible(true);
        enabled.set(instanceMetadata, false);
        
        namingMetadataManager.updateInstanceMetadata(service, METADATA_ID, instanceMetadata);
        
        Optional<InstanceMetadata> optional = namingMetadataManager.getInstanceMetadata(service, METADATA_ID);
        assertTrue(optional.isPresent());
        assertNotNull(optional.get());
        assertFalse(optional.get().isEnabled());
    }
    
    @Test
    void testRemoveServiceMetadata() {
        
        namingMetadataManager.removeServiceMetadata(service);
        
        Optional<ServiceMetadata> serviceMetadata = namingMetadataManager.getServiceMetadata(service);
        
        assertFalse(serviceMetadata.isPresent());
    }
    
    @Test
    void testRemoveInstanceMetadata() {
        
        namingMetadataManager.removeInstanceMetadata(service, METADATA_ID);
        
        Optional<InstanceMetadata> instanceMetadata = namingMetadataManager.getInstanceMetadata(service, METADATA_ID);
        
        assertFalse(instanceMetadata.isPresent());

        Map<Service, ConcurrentMap<String, InstanceMetadata>> instanceMetadataSnapshot = namingMetadataManager.getInstanceMetadataSnapshot();
        
        assertEquals(0, instanceMetadataSnapshot.size());
    }
    
    @Test
    void testGetServiceMetadataSnapshot() {
        Map<Service, ServiceMetadata> serviceMetadataSnapshot = namingMetadataManager.getServiceMetadataSnapshot();
        
        assertEquals(1, serviceMetadataSnapshot.size());
    }
    
    @Test
    void testGetInstanceMetadataSnapshot() {
        Map<Service, ConcurrentMap<String, InstanceMetadata>> instanceMetadataSnapshot = namingMetadataManager.getInstanceMetadataSnapshot();
        
        assertEquals(1, instanceMetadataSnapshot.size());
    }
    
    @Test
    void testLoadServiceMetadataSnapshot() {
        namingMetadataManager.loadServiceMetadataSnapshot(new ConcurrentHashMap<>());
        Map<Service, ServiceMetadata> serviceMetadataSnapshot = namingMetadataManager.getServiceMetadataSnapshot();
        
        assertEquals(0, serviceMetadataSnapshot.size());
    }
    
    @Test
    void testLoadInstanceMetadataSnapshot() {
        namingMetadataManager.loadInstanceMetadataSnapshot(new ConcurrentHashMap<>());
        Map<Service, ConcurrentMap<String, InstanceMetadata>> instanceMetadataSnapshot = namingMetadataManager.getInstanceMetadataSnapshot();
        
        assertEquals(0, instanceMetadataSnapshot.size());
    }
    
    @Test
    void testGetExpiredMetadataInfos() {
        Set<ExpiredMetadataInfo> expiredMetadataInfos = namingMetadataManager.getExpiredMetadataInfos();
        
        assertNotNull(expiredMetadataInfos);
    }
    
    @Test
    void testSubscribeTypes() {
        List<Class<? extends Event>> classes = namingMetadataManager.subscribeTypes();
        
        assertEquals(3, classes.size());
    }
    
    @Test
    void testOnEvent() {
        Mockito.when(instanceMetadataEvent.getService()).thenReturn(service);
        Mockito.when(instanceMetadataEvent.getMetadataId()).thenReturn(METADATA_ID);
        Mockito.when(serviceMetadataEvent.getService()).thenReturn(service);
        Mockito.when(clientDisconnectEvent.getClient()).thenReturn(client);
        
        namingMetadataManager.onEvent(instanceMetadataEvent);
        Mockito.verify(instanceMetadataEvent, Mockito.times(2)).getMetadataId();
        Mockito.verify(instanceMetadataEvent, Mockito.times(2)).getService();
        
        namingMetadataManager.onEvent(serviceMetadataEvent);
        Mockito.verify(serviceMetadataEvent).getService();
        
        namingMetadataManager.onEvent(clientDisconnectEvent);
        Mockito.verify(clientDisconnectEvent).getClient();
    }
}
