/*
 * Copyright 1999-2025 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.ai.service;

import com.alibaba.nacos.ai.constant.Constants;
import com.alibaba.nacos.api.ai.constant.AiConstants;
import com.alibaba.nacos.api.ai.model.mcp.McpEndpointSpec;
import com.alibaba.nacos.api.ai.model.mcp.McpServiceRef;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.api.NacosApiException;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.naming.core.InstanceOperator;
import com.alibaba.nacos.naming.core.ServiceOperator;
import com.alibaba.nacos.naming.core.v2.ServiceManager;
import com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager;
import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata;
import com.alibaba.nacos.naming.core.v2.pojo.Service;
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.junit.jupiter.MockitoExtension;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class McpEndpointOperationServiceTest {
    
    @Mock
    private ServiceOperator serviceOperator;
    
    @Mock
    private InstanceOperator instanceOperator;
    
    @Mock
    private NamingMetadataManager metadataManager;
    
    McpEndpointOperationService endpointOperationService;
    
    @BeforeEach
    void setUp() {
        endpointOperationService = new McpEndpointOperationService(serviceOperator, instanceOperator, metadataManager);
    }
    
    @AfterEach
    void tearDown() {
        Service service = Service.newService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP,
                "mcpName::1.0.0");
        ServiceManager.getInstance().removeSingleton(service);
    }
    
    @Test
    void createMcpServerEndpointServiceIfNecessaryTypeRefWithoutMsg() {
        McpEndpointSpec mcpEndpointSpec = new McpEndpointSpec();
        mcpEndpointSpec.setType(AiConstants.Mcp.MCP_ENDPOINT_TYPE_REF);
        assertThrows(NacosApiException.class, () -> endpointOperationService.createMcpServerEndpointServiceIfNecessary(
                        AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName", "1.0.0", mcpEndpointSpec),
                "`namespaceId`, `groupName`, `serviceName` should be in remoteServerConfig data if type is `REF`");
        mcpEndpointSpec.getData().put("namespaceId", AiConstants.Mcp.MCP_DEFAULT_NAMESPACE);
        assertThrows(NacosApiException.class, () -> endpointOperationService.createMcpServerEndpointServiceIfNecessary(
                        AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName", "1.0.0", mcpEndpointSpec),
                "`namespaceId`, `groupName`, `serviceName` should be in remoteServerConfig data if type is `REF`");
        mcpEndpointSpec.getData().put("groupName", "groupName");
        assertThrows(NacosApiException.class, () -> endpointOperationService.createMcpServerEndpointServiceIfNecessary(
                        AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName", "1.0.0", mcpEndpointSpec),
                "`namespaceId`, `groupName`, `serviceName` should be in remoteServerConfig data if type is `REF`");
    }
    
    @Test
    void createMcpServerEndpointServiceIfNecessaryTypeRef() throws NacosException {
        McpEndpointSpec mcpEndpointSpec = new McpEndpointSpec();
        mcpEndpointSpec.setType(AiConstants.Mcp.MCP_ENDPOINT_TYPE_REF);
        mcpEndpointSpec.getData().put("namespaceId", AiConstants.Mcp.MCP_DEFAULT_NAMESPACE);
        mcpEndpointSpec.getData().put("groupName", "groupName");
        mcpEndpointSpec.getData().put("serviceName", "serviceName");
        Service service = endpointOperationService.createMcpServerEndpointServiceIfNecessary(
                AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName", "1.0.0", mcpEndpointSpec);
        assertEquals(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, service.getNamespace());
        assertEquals("groupName", service.getGroup());
        assertEquals("serviceName", service.getName());
    }
    
    @Test
    void createMcpServerEndpointServiceIfNecessaryTypeDirectWithoutExistService() throws NacosException {
        McpEndpointSpec mcpEndpointSpec = new McpEndpointSpec();
        mcpEndpointSpec.setType(AiConstants.Mcp.MCP_ENDPOINT_TYPE_DIRECT);
        mcpEndpointSpec.getData().put("address", "127.0.0.1");
        mcpEndpointSpec.getData().put("port", "8848");
        Service service = endpointOperationService.createMcpServerEndpointServiceIfNecessary(
                AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName", "1.0.0", mcpEndpointSpec);
        assertEquals(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, service.getNamespace());
        assertEquals(Constants.MCP_SERVER_ENDPOINT_GROUP, service.getGroup());
        assertEquals("mcpName::1.0.0", service.getName());
        verify(serviceOperator).create(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE),
                eq(NamingUtils.getGroupedName("mcpName::1.0.0", Constants.MCP_SERVER_ENDPOINT_GROUP)),
                any(ServiceMetadata.class));
        verify(instanceOperator).registerInstance(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE),
                eq(Constants.MCP_SERVER_ENDPOINT_GROUP), eq("mcpName::1.0.0"), any(Instance.class));
    }
    
    @Test
    void createMcpServerEndpointServiceIfNecessaryTypeDirectWithExistService() throws NacosException {
        ServiceManager.getInstance().getSingleton(
                Service.newService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP,
                        "mcpName::1.0.0"));
        McpEndpointSpec mcpEndpointSpec = new McpEndpointSpec();
        mcpEndpointSpec.setType(AiConstants.Mcp.MCP_ENDPOINT_TYPE_DIRECT);
        mcpEndpointSpec.getData().put("address", "127.0.0.1");
        mcpEndpointSpec.getData().put("port", "8848");
        Service service = endpointOperationService.createMcpServerEndpointServiceIfNecessary(
                AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName", "1.0.0", mcpEndpointSpec);
        assertEquals(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, service.getNamespace());
        assertEquals(Constants.MCP_SERVER_ENDPOINT_GROUP, service.getGroup());
        assertEquals("mcpName::1.0.0", service.getName());
        verify(serviceOperator, never()).create(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE),
                eq(NamingUtils.getGroupedName("mcpName::1.0.0", Constants.MCP_SERVER_ENDPOINT_GROUP)),
                any(ServiceMetadata.class));
        verify(instanceOperator).registerInstance(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE),
                eq(Constants.MCP_SERVER_ENDPOINT_GROUP), eq("mcpName::1.0.0"), any(Instance.class));
    }
    
    @Test
    void getMcpServerEndpointInstances() throws NacosException {
        Instance instance = new Instance();
        instance.setIp("127.0.0.1");
        instance.setPort(8848);
        ServiceInfo serviceInfo = new ServiceInfo();
        serviceInfo.setHosts(Collections.singletonList(instance));
        when(instanceOperator.listInstance(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP,
                "mcpName", null, "", true)).thenReturn(serviceInfo);
        McpServiceRef serviceRef = new McpServiceRef();
        serviceRef.setNamespaceId(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE);
        serviceRef.setGroupName(Constants.MCP_SERVER_ENDPOINT_GROUP);
        serviceRef.setServiceName("mcpName");
        List<Instance> actual = endpointOperationService.getMcpServerEndpointInstances(serviceRef);
        assertEquals(1, actual.size());
        assertEquals("127.0.0.1", actual.get(0).getIp());
        assertEquals(8848, actual.get(0).getPort());
    }
    
    @Test
    void deleteMcpServerEndpointServiceForNonExistService() throws NacosException {
        endpointOperationService.deleteMcpServerEndpointService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName");
        verify(instanceOperator, never()).removeInstance(anyString(), anyString(), anyString(), any(Instance.class));
        verify(serviceOperator, never()).delete(anyString(), anyString());
    }
    
    @Test
    void deleteMcpServerEndpointServiceForRefService() throws NacosException {
        Service service = Service.newService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP,
                "mcpName");
        ServiceManager.getInstance().getSingleton(service);
        when(metadataManager.getServiceMetadata(service)).thenReturn(Optional.empty());
        endpointOperationService.deleteMcpServerEndpointService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName");
        verify(instanceOperator, never()).removeInstance(anyString(), anyString(), anyString(), any(Instance.class));
        verify(serviceOperator, never()).delete(anyString(), anyString());
    }
    
    @Test
    void deleteMcpServerEndpointService() throws NacosException {
        Service service = Service.newService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP,
                "mcpName");
        ServiceManager.getInstance().getSingleton(service);
        ServiceMetadata serviceMetadata = new ServiceMetadata();
        serviceMetadata.getExtendData()
                .put(Constants.MCP_SERVER_ENDPOINT_METADATA_MARK, Constants.MCP_SERVER_ENDPOINT_METADATA_MARK);
        when(metadataManager.getServiceMetadata(service)).thenReturn(Optional.of(serviceMetadata));
        List<Instance> instances = new LinkedList<>();
        Instance instance = new Instance();
        instance.setIp("127.0.0.1");
        instance.setPort(8848);
        instances.add(instance);
        instance = new Instance();
        instance.setIp("127.0.0.1");
        instance.setPort(9848);
        instances.add(instance);
        ServiceInfo serviceInfo = new ServiceInfo();
        serviceInfo.setHosts(instances);
        when(instanceOperator.listInstance(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP,
                "mcpName", null, "", false)).thenReturn(serviceInfo);
        endpointOperationService.deleteMcpServerEndpointService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "mcpName");
        for (Instance each : instances) {
            verify(instanceOperator).removeInstance(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE,
                    Constants.MCP_SERVER_ENDPOINT_GROUP, "mcpName", each);
        }
        verify(serviceOperator).delete(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE,
                NamingUtils.getGroupedName("mcpName", Constants.MCP_SERVER_ENDPOINT_GROUP));
    }
}