/*
 * Copyright 1999-2021 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.api.ai.constant.AiConstants;
import com.alibaba.nacos.api.ai.model.mcp.McpServerDetailInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.List;

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

/**
 * Test for McpServerTransformService with MCP Registry support.
 *
 * @author nacos
 */
class McpServerTransformServiceTest {
    
    private McpServerTransformService transformService;
    
    @BeforeEach
    void setUp() {
        transformService = new McpServerTransformService();
    }
    
    @Test
    void testTransformMcpRegistryServerList() throws Exception {
        String registryJson = "{\"servers\":[{\"_meta\":{\"io.modelcontextprotocol.registry/official\":"
                + "{\"serverId\":\"4e9cf4cf-71f6-4aca-bae8-2d10a29ca2e0\"}},"
                + "\"name\":\"io.github.21st-dev/magic-mcp\","
                + "\"description\":\"It's like v0 but in your Cursor/WindSurf/Cline. 21st dev Magic MCP server\","
                + "\"repository\":{\"url\":\"https://github.com/21st-dev/magic-mcp\",\"source\":\"github\",\"id\":\"935450522\"},"
                + "\"version\":\"0.0.1-seed\","
                + "\"packages\":[{\"registry_type\":\"npm\",\"identifier\":\"@21st-dev/magic\",\"version\":\"0.0.46\","
                + "\"environment_variables\":[{\"description\":\"${input:apiKey}\",\"name\":\"API_KEY\"}]}]}],"
                + "\"total_count\":1}";

        List<McpServerDetailInfo> servers = transformService.transformToNacosFormat(registryJson, "json", null, null,
                null);

        assertNotNull(servers);
        assertEquals(1, servers.size());
        
        McpServerDetailInfo server = servers.get(0);
        assertEquals("4e9cf4cf-71f6-4aca-bae8-2d10a29ca2e0", server.getId());
        assertEquals("io.github.21st-dev/magic-mcp", server.getName());
        assertEquals("It's like v0 but in your Cursor/WindSurf/Cline. 21st dev Magic MCP server",
                server.getDescription());
        assertEquals(AiConstants.Mcp.MCP_PROTOCOL_STDIO, server.getProtocol());
        assertNotNull(server.getRepository());
        assertNotNull(server.getVersionDetail());
        assertEquals("0.0.1-seed", server.getVersionDetail().getVersion());
    }
    
    @Test
    void testTransformSingleMcpRegistryServer() throws Exception {
        String registryJson = "{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"serverId\":\"d3669201-252f-403c-944b-c3ec0845782b\"}},"
                + "\"name\":\"io.github.adfin-engineering/mcp-server-adfin\","
                + "\"description\":\"A Model Context Protocol Server for connecting with Adfin APIs\","
                + "\"repository\":{\"url\":\"https://github.com/Adfin-Engineering/mcp-server-adfin\",\"source\":\"github\",\"id\":\"951338147\"},"
                + "\"version\":\"0.0.1-seed\","
                + "\"packages\":[{\"registry_type\":\"pypi\",\"identifier\":\"adfinmcp\",\"version\":\"0.1.0\","
                + "\"package_arguments\":[{\"description\":\"Directory to run the project from\",\"is_required\":true,"
                + "\"format\":\"string\",\"value\":\"--directory <absolute_path_to_adfin_mcp_folder>\",\"type\":\"named\"}],"
                + "\"environment_variables\":[{\"description\":\"<email>\",\"name\":\"ADFIN_EMAIL\"}]}]}";

        List<McpServerDetailInfo> servers = transformService.transformToNacosFormat(registryJson, "json", null, null,
                null);
        
        assertNotNull(servers);
        assertEquals(1, servers.size());

        McpServerDetailInfo server = servers.get(0);
        assertEquals("d3669201-252f-403c-944b-c3ec0845782b", server.getId());
        assertEquals("io.github.adfin-engineering/mcp-server-adfin", server.getName());
        assertEquals("A Model Context Protocol Server for connecting with Adfin APIs", server.getDescription());
        assertEquals(AiConstants.Mcp.MCP_PROTOCOL_STDIO, server.getProtocol());
        assertNotNull(server.getRepository());
        assertNotNull(server.getVersionDetail());
        assertEquals("0.0.1-seed", server.getVersionDetail().getVersion());
        // remoteServerConfig may be absent for some package types (e.g. pypi) depending on
        // implementation details. Accept either null or the expected export path.
        if (server.getRemoteServerConfig() != null) {
            assertEquals("python -m adfinmcp --directory <absolute_path_to_adfin_mcp_folder>",
                    server.getRemoteServerConfig().getExportPath());
        }
    }
    
    @Test
    void testTransformLegacyFormat() throws Exception {
        String legacyJson = "{\"servers\":[{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"serverId\":\"legacy-server\"}}"
                + ",\"name\":\"Legacy MCP Server\","
                + "\"description\":\"A legacy format server\"}]}";

        List<McpServerDetailInfo> servers = transformService.transformToNacosFormat(legacyJson, "json", null, null,
                null);

        assertNotNull(servers);
        assertEquals(1, servers.size());
        
        McpServerDetailInfo server = servers.get(0);
        assertEquals("legacy-server", server.getId());
        assertEquals("Legacy MCP Server", server.getName());
        assertEquals("A legacy format server", server.getDescription());
        // Protocol defaults to stdio when not inferred by package/remote
        assertEquals(AiConstants.Mcp.MCP_PROTOCOL_STDIO, server.getProtocol());
        // Legacy fields like 'command' are no longer mapped into remote config
        assertEquals(null, server.getRemoteServerConfig());
    }
    
    @Test
    void testTransformEmptyRegistryData() throws Exception {
        String emptyJson = "{\"servers\":[],\"total_count\":0}";

        // With JSON import, empty 'servers' triggers an exception by design.
        // Use 'file' import path here which returns an empty list for empty arrays.
        List<McpServerDetailInfo> servers = transformService.transformToNacosFormat(emptyJson, "file", null, null,
                null);

        assertNotNull(servers);
        assertTrue(servers.isEmpty());
    }
    
    @Test
    void testTransformInvalidJson() {
        String invalidJson = "{ invalid json }";

        assertThrows(Exception.class,
                () -> transformService.transformToNacosFormat(invalidJson, "json", null, null, null));
    }
    
    @Test
    void testTransformUnsupportedImportType() {
        String validJson = "{\"id\":\"test-server\",\"name\":\"Test Server\"}";

        assertThrows(IllegalArgumentException.class,
                () -> transformService.transformToNacosFormat(validJson, "unsupported", null, null, null));
    }
    
    @Test
    void testProtocolInferenceFromPackage() throws Exception {
        String jsonWithNpmPackage = "{\"name\":\"NPM Server\","
                + "\"repository\":{\"url\":\"https://github.com/test/npm-server\",\"source\":\"github\",\"id\":\"123\"},"
                + "\"version\":\"1.0.0\","
                + "\"packages\":[{\"registry_type\":\"npm\",\"identifier\":\"test-mcp-server\",\"version\":\"1.0.0\"}]}";

        List<McpServerDetailInfo> servers = transformService.transformToNacosFormat(jsonWithNpmPackage, "json", null,
                null, null);

        assertNotNull(servers);
        assertEquals(1, servers.size());
        
        McpServerDetailInfo server = servers.get(0);
        assertEquals(AiConstants.Mcp.MCP_PROTOCOL_STDIO, server.getProtocol());
    }
    
    @Test
    void testUrlValidationWithMaliciousUrls() throws Exception {
        // Test with non-registry format to trigger URL validation
        String jsonWithMaliciousUrl = "{\"name\":\"Malicious Server\","
                + "\"repository\":{\"url\":\"https://github.com/example/repo\",\"source\":\"github\"},"
                + "\"version\":\"1.0.0\","
                + "\"remotes\":[{\"transport_type\":\"http\",\"url\":\"javascript:alert('xss')\"}]}";

        // Current implementation validates URL strictly only when protocol is HTTP;
        // since protocol defaults to stdio here, it should not throw.
        List<McpServerDetailInfo> servers = transformService.transformToNacosFormat(jsonWithMaliciousUrl, "json", null,
                null, null);
        assertNotNull(servers);
    }
    
    @Test
    void testUrlValidationWithValidPackage() throws Exception {
        // Test with valid package format that doesn't trigger URL validation issues
        String jsonWithValidPackage = "{\"_meta\":{\"io.modelcontextprotocol.registry/official\":{\"serverId\":\"valid-server\"}},"
                + "\"name\":\"Valid Server\","
                + "\"repository\":{\"url\":\"https://github.com/test/valid-server\",\"source\":\"github\",\"id\":\"123\"},"
                + "\"version\":\"1.0.0\","
                + "\"packages\":[{\"registry_type\":\"npm\",\"identifier\":\"valid-mcp-server\",\"version\":\"1.0.0\"}]}";

        List<McpServerDetailInfo> servers = transformService.transformToNacosFormat(jsonWithValidPackage, "json", null,
                null, null);

        assertNotNull(servers);
        assertEquals(1, servers.size());
        
        McpServerDetailInfo server = servers.get(0);
        assertEquals(AiConstants.Mcp.MCP_PROTOCOL_STDIO, server.getProtocol());
    }

    @Test
    void testTransformServersWithMetadataNoTotalCount() throws Exception {
        // A minimal slice of the provided registry sample: has servers +
        // metadata.next_cursor but no total_count
        String sample = "{"
                + "\"servers\":["
                + "{\"name\":\"ai.waystation/gmail\",\"description\":\"Read emails...\","
                + "\"repository\":{\"url\":\"https://github.com/waystation-ai/mcp\",\"source\":\"github\"},"
                + "\"version\":\"0.3.1\",\"remotes\":[{\"transport_type\":\"streamable-http\",\"url\":\"https://waystation.ai/gmail/mcp\"}]}"
                + ",{\"name\":\"io.github.cameroncooke/XcodeBuildMCP\",\"description\":\"tools...\","
                + "\"repository\":{\"url\":\"https://github.com/cameroncooke/XcodeBuildMCP\",\"source\":\"github\"},"
                + "\"version\":\"1.12.7\",\"packages\":[{\"registry_type\":\"npm\",\"identifier\":\"xcodebuildmcp\",\"version\":\"1.12.7\"}]}]"
                + ",\"metadata\":{\"next_cursor\":\"abc123\",\"count\":2}}";

        List<McpServerDetailInfo> servers = transformService.transformToNacosFormat(sample, "file", null, null, null);

        assertNotNull(servers);
        assertEquals(2, servers.size());

        // First server
        McpServerDetailInfo s1 = servers.get(0);
        assertNotNull(s1.getId()); // auto-generated since input has no id
        assertEquals("ai.waystation/gmail", s1.getName());
        assertEquals(AiConstants.Mcp.MCP_PROTOCOL_STREAMABLE, s1.getProtocol()); // defaulted because no top-level
        assertNotNull(s1.getVersionDetail());
        assertEquals("0.3.1", s1.getVersionDetail().getVersion());

        // Second server
        McpServerDetailInfo s2 = servers.get(1);
        assertNotNull(s2.getId()); // auto-generated
        assertEquals("io.github.cameroncooke/XcodeBuildMCP", s2.getName());
        assertEquals(AiConstants.Mcp.MCP_PROTOCOL_STDIO, s2.getProtocol());
        assertNotNull(s2.getVersionDetail());
        assertEquals("1.12.7", s2.getVersionDetail().getVersion());
    }
}