/*
 * 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.remote.handler;

import com.alibaba.nacos.ai.constant.Constants;
import com.alibaba.nacos.ai.index.McpServerIndex;
import com.alibaba.nacos.ai.model.mcp.McpServerIndexData;
import com.alibaba.nacos.ai.service.McpEndpointOperationService;
import com.alibaba.nacos.ai.service.McpServerOperationService;
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.McpServerBasicInfo;
import com.alibaba.nacos.api.ai.model.mcp.McpServerDetailInfo;
import com.alibaba.nacos.api.ai.model.mcp.registry.ServerVersionDetail;
import com.alibaba.nacos.api.ai.remote.request.ReleaseMcpServerRequest;
import com.alibaba.nacos.api.ai.remote.response.ReleaseMcpServerResponse;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.exception.api.NacosApiException;
import com.alibaba.nacos.api.model.v2.ErrorCode;
import com.alibaba.nacos.api.remote.request.RequestMeta;
import com.alibaba.nacos.api.remote.response.ResponseCode;
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.UUID;

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

@ExtendWith(MockitoExtension.class)
class ReleaseMcpServerRequestHandlerTest {
    
    @Mock
    private McpServerOperationService mcpServerOperationService;
    
    @Mock
    private McpEndpointOperationService endpointOperationService;
    
    @Mock
    private McpServerIndex mcpServerIndex;
    
    @Mock
    private RequestMeta meta;
    
    ReleaseMcpServerRequestHandler requestHandler;
    
    @BeforeEach
    void setUp() {
        requestHandler = new ReleaseMcpServerRequestHandler(mcpServerOperationService, endpointOperationService,
                mcpServerIndex);
    }
    
    @AfterEach
    void tearDown() {
    }
    
    @Test
    void handleWithInvalidParameter() throws NacosException {
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        ReleaseMcpServerResponse response = requestHandler.handle(request, null);
        assertErrorResponse(response, NacosException.INVALID_PARAM,
                "Required parameter 'serverSpecification' type McpServerBasicInfo is not present");
        McpServerBasicInfo serverSpecification = new McpServerBasicInfo();
        request.setServerSpecification(serverSpecification);
        response = requestHandler.handle(request, null);
        assertErrorResponse(response, NacosException.INVALID_PARAM,
                "Required parameter 'serverSpecification.name' type String is not present");
        serverSpecification.setName("test");
        response = requestHandler.handle(request, null);
        assertErrorResponse(response, NacosException.INVALID_PARAM,
                "Required parameter `serverSpecification.versionDetail.version` not present");
        ServerVersionDetail serverVersionDetail = new ServerVersionDetail();
        serverSpecification.setVersionDetail(serverVersionDetail);
        response = requestHandler.handle(request, null);
        assertErrorResponse(response, NacosException.INVALID_PARAM,
                "Required parameter `serverSpecification.versionDetail.version` not present");
    }
    
    @Test
    void handleReleaseExistedServerAndVersion() throws NacosException {
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        request.setServerSpecification(buildMockServerSpecification(false, false));
        McpServerDetailInfo detailInfo = buildMockServerDetail();
        when(mcpServerOperationService.getMcpServerDetail(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, null, "test",
                "1.0.0")).thenReturn(detailInfo);
        when(meta.getConnectionId()).thenReturn("111");
        ReleaseMcpServerResponse response = requestHandler.handle(request, meta);
        assertErrorResponse(response, NacosException.CONFLICT,
                "Mcp Server test and target version 1.0.0 already exist, do not do release");
    }
    
    @Test
    void handleReleaseNewServerForSse() throws NacosException {
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        request.setServerSpecification(buildMockServerSpecification(false, false));
        String id = UUID.randomUUID().toString();
        when(mcpServerOperationService.getMcpServerDetail(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, null, "test",
                "1.0.0")).thenThrow(
                    new NacosApiException(NacosException.NOT_FOUND, ErrorCode.MCP_SERVER_NOT_FOUND, ""));
        when(endpointOperationService.generateService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "test::1.0.0")).thenReturn(
                Service.newService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP, "test"));
        when(mcpServerOperationService.createMcpServer(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE),
                eq(request.getServerSpecification()), isNull(), isNotNull())).thenReturn(id);
        when(meta.getConnectionId()).thenReturn("111");
        ReleaseMcpServerResponse response = requestHandler.handle(request, meta);
        assertEquals(id, response.getMcpId());
    }
    
    @Test
    void handleReleaseNewServerForSseWithSpecifiedEndpoint() throws NacosException {
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        request.setServerSpecification(buildMockServerSpecification(false, false));
        request.setEndpointSpecification(new McpEndpointSpec());
        String id = UUID.randomUUID().toString();
        when(mcpServerOperationService.getMcpServerDetail(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, null, "test",
                "1.0.0")).thenThrow(
                    new NacosApiException(NacosException.NOT_FOUND, ErrorCode.MCP_SERVER_NOT_FOUND, ""));
        when(mcpServerOperationService.createMcpServer(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE),
                eq(request.getServerSpecification()), isNull(), isNotNull())).thenReturn(id);
        when(meta.getConnectionId()).thenReturn("111");
        ReleaseMcpServerResponse response = requestHandler.handle(request, meta);
        assertEquals(id, response.getMcpId());
        verify(endpointOperationService, never()).generateService(anyString(), anyString());
    }
    
    @Test
    void handleReleaseNewServerForStdio() throws NacosException {
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        request.setServerSpecification(buildMockServerSpecification(true, false));
        String id = UUID.randomUUID().toString();
        when(mcpServerOperationService.getMcpServerDetail(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, null, "test",
                "1.0.0")).thenThrow(
                    new NacosApiException(NacosException.NOT_FOUND, ErrorCode.MCP_SERVER_NOT_FOUND, ""));
        when(mcpServerOperationService.createMcpServer(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE),
                eq(request.getServerSpecification()), isNull(), isNull())).thenReturn(id);
        when(meta.getConnectionId()).thenReturn("111");
        ReleaseMcpServerResponse response = requestHandler.handle(request, meta);
        assertEquals(id, response.getMcpId());
    }
    
    @Test
    void handleReleaseNewVersionWithoutLatest() throws NacosException {
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        request.setServerSpecification(buildMockServerSpecification(false, false));
        String id = UUID.randomUUID().toString();
        when(mcpServerOperationService.getMcpServerDetail(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, null, "test",
                "1.0.0")).thenThrow(
                    new NacosApiException(NacosException.NOT_FOUND, ErrorCode.MCP_SEVER_VERSION_NOT_FOUND, ""));
        when(meta.getConnectionId()).thenReturn("111");
        McpServerIndexData indexData = McpServerIndexData.newIndexData(id, AiConstants.Mcp.MCP_DEFAULT_NAMESPACE);
        when(mcpServerIndex.getMcpServerByName(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "test")).thenReturn(indexData);
        when(endpointOperationService.generateService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "test::1.0.0")).thenReturn(
                Service.newService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP, "test"));
        ReleaseMcpServerResponse response = requestHandler.handle(request, meta);
        assertEquals(id, response.getMcpId());
        verify(mcpServerOperationService).updateMcpServer(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE), eq(false),
                eq(request.getServerSpecification()), isNull(), isNotNull());
    }
    
    @Test
    void handleReleaseNewVersionWithoutLatestWithSpecifiedEndpoint() throws NacosException {
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        request.setServerSpecification(buildMockServerSpecification(false, false));
        request.setEndpointSpecification(new McpEndpointSpec());
        String id = UUID.randomUUID().toString();
        when(mcpServerOperationService.getMcpServerDetail(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, null, "test",
                "1.0.0")).thenThrow(
                    new NacosApiException(NacosException.NOT_FOUND, ErrorCode.MCP_SEVER_VERSION_NOT_FOUND, ""));
        when(meta.getConnectionId()).thenReturn("111");
        McpServerIndexData indexData = McpServerIndexData.newIndexData(id, AiConstants.Mcp.MCP_DEFAULT_NAMESPACE);
        when(mcpServerIndex.getMcpServerByName(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "test")).thenReturn(indexData);
        ReleaseMcpServerResponse response = requestHandler.handle(request, meta);
        assertEquals(id, response.getMcpId());
        verify(mcpServerOperationService).updateMcpServer(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE), eq(false),
                eq(request.getServerSpecification()), isNull(), isNotNull());
        verify(endpointOperationService, never()).generateService(anyString(), anyString());
    }
    
    @Test
    void handleReleaseNewVersionWithLatest() throws NacosException {
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        request.setServerSpecification(buildMockServerSpecification(false, true));
        String id = UUID.randomUUID().toString();
        when(mcpServerOperationService.getMcpServerDetail(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, null, "test",
                "1.0.0")).thenThrow(
                    new NacosApiException(NacosException.NOT_FOUND, ErrorCode.MCP_SEVER_VERSION_NOT_FOUND, ""));
        when(meta.getConnectionId()).thenReturn("111");
        McpServerIndexData indexData = McpServerIndexData.newIndexData(id, AiConstants.Mcp.MCP_DEFAULT_NAMESPACE);
        when(mcpServerIndex.getMcpServerByName(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "test")).thenReturn(indexData);
        when(endpointOperationService.generateService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, "test::1.0.0")).thenReturn(
                Service.newService(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, Constants.MCP_SERVER_ENDPOINT_GROUP, "test"));
        ReleaseMcpServerResponse response = requestHandler.handle(request, meta);
        assertEquals(id, response.getMcpId());
        verify(mcpServerOperationService).updateMcpServer(eq(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE), eq(true),
                eq(request.getServerSpecification()), isNull(), isNotNull());
    }
    
    @Test
    void handleReleaseWithException() throws NacosException {
        NacosApiException exceptedException = new NacosApiException(NacosException.SERVER_ERROR, ErrorCode.SERVER_ERROR,
                "test");
        ReleaseMcpServerRequest request = new ReleaseMcpServerRequest();
        request.setServerSpecification(buildMockServerSpecification(false, true));
        when(mcpServerOperationService.getMcpServerDetail(AiConstants.Mcp.MCP_DEFAULT_NAMESPACE, null, "test",
                "1.0.0")).thenThrow(exceptedException);
        try {
            requestHandler.handle(request, meta);
        } catch (NacosApiException e) {
            assertEquals(exceptedException, e);
        }
    }
    
    private McpServerBasicInfo buildMockServerSpecification(boolean isStdio, boolean isLatest) {
        McpServerBasicInfo result = new McpServerBasicInfo();
        result.setName("test");
        ServerVersionDetail serverVersionDetail = new ServerVersionDetail();
        serverVersionDetail.setVersion("1.0.0");
        if (isLatest) {
            serverVersionDetail.setIs_latest(true);
        }
        result.setVersionDetail(serverVersionDetail);
        if (!isStdio) {
            result.setProtocol(AiConstants.Mcp.MCP_PROTOCOL_SSE);
            result.setFrontProtocol(AiConstants.Mcp.MCP_PROTOCOL_SSE);
        } else {
            result.setProtocol(AiConstants.Mcp.MCP_PROTOCOL_STDIO);
            result.setFrontProtocol(AiConstants.Mcp.MCP_PROTOCOL_STDIO);
        }
        return result;
    }
    
    private McpServerDetailInfo buildMockServerDetail() {
        McpServerDetailInfo result = new McpServerDetailInfo();
        result.setName("test");
        ServerVersionDetail serverVersionDetail = new ServerVersionDetail();
        serverVersionDetail.setVersion("1.0.0");
        result.setVersionDetail(serverVersionDetail);
        result.setId(UUID.randomUUID().toString());
        return result;
    }
    
    private void assertErrorResponse(ReleaseMcpServerResponse response, int code, String message) {
        assertEquals(ResponseCode.FAIL.getCode(), response.getResultCode());
        assertEquals(code, response.getErrorCode());
        assertEquals(message, response.getMessage());
    }
}