"""
Copyright (C) 2025 Luciano Guerche

This file is part of rabbitmq-mcp-server.

rabbitmq-mcp-server is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

rabbitmq-mcp-server is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with rabbitmq-mcp-server. If not, see <https://www.gnu.org/licenses/>.
"""

"""Unit tests for JSON-RPC 2.0 message handling."""

import json

import pytest

from mcp_server.jsonrpc import (
    INVALID_PARAMS,
    INVALID_REQUEST,
    METHOD_NOT_FOUND,
    PARSE_ERROR,
    SERVER_ERROR,
    InvalidParamsError,
    InvalidRequestError,
    MethodNotFoundError,
    ParseError,
    ServerError,
    build_error_response,
    build_response,
    serialize_response,
    validate_request,
)


class TestValidateRequest:
    """Test JSON-RPC request validation."""

    def test_valid_request(self) -> None:
        """Test valid JSON-RPC request parsing."""
        request = '{"jsonrpc": "2.0", "id": 1, "method": "test", "params": {}}'
        result = validate_request(request)

        assert result["jsonrpc"] == "2.0"
        assert result["id"] == 1
        assert result["method"] == "test"
        assert result["params"] == {}

    def test_invalid_json_returns_parse_error(self) -> None:
        """Test invalid JSON returns PARSE_ERROR."""
        with pytest.raises(ParseError) as exc_info:
            validate_request("not valid json")

        assert exc_info.value.code == PARSE_ERROR

    def test_missing_jsonrpc_field(self) -> None:
        """Test missing jsonrpc field returns INVALID_REQUEST."""
        request = '{"id": 1, "method": "test"}'

        with pytest.raises(InvalidRequestError) as exc_info:
            validate_request(request)

        assert exc_info.value.code == INVALID_REQUEST
        assert "jsonrpc" in exc_info.value.message

    def test_wrong_jsonrpc_version(self) -> None:
        """Test wrong jsonrpc version returns INVALID_REQUEST."""
        request = '{"jsonrpc": "1.0", "id": 1, "method": "test"}'

        with pytest.raises(InvalidRequestError):
            validate_request(request)

    def test_missing_id_field(self) -> None:
        """Test missing id field returns INVALID_REQUEST."""
        request = '{"jsonrpc": "2.0", "method": "test"}'

        with pytest.raises(InvalidRequestError) as exc_info:
            validate_request(request)

        assert "id" in exc_info.value.message

    def test_missing_method_field(self) -> None:
        """Test missing method field returns INVALID_REQUEST."""
        request = '{"jsonrpc": "2.0", "id": 1}'

        with pytest.raises(InvalidRequestError) as exc_info:
            validate_request(request)

        assert "method" in exc_info.value.message

    def test_invalid_method_type(self) -> None:
        """Test method must be string."""
        request = '{"jsonrpc": "2.0", "id": 1, "method": 123}'

        with pytest.raises(InvalidRequestError) as exc_info:
            validate_request(request)

        assert "method" in exc_info.value.message

    def test_invalid_params_type(self) -> None:
        """Test params must be object or array."""
        request = '{"jsonrpc": "2.0", "id": 1, "method": "test", "params": "invalid"}'

        with pytest.raises(InvalidRequestError) as exc_info:
            validate_request(request)

        assert "params" in exc_info.value.message

    def test_request_not_object(self) -> None:
        """Test request must be an object."""
        with pytest.raises(InvalidRequestError) as exc_info:
            validate_request('["not", "an", "object"]')

        assert "object" in exc_info.value.message


class TestBuildResponse:
    """Test JSON-RPC response builders."""

    def test_build_success_response(self) -> None:
        """Test building success response."""
        response = build_response(request_id=1, result={"status": "ok"})

        assert response["jsonrpc"] == "2.0"
        assert response["id"] == 1
        assert response["result"] == {"status": "ok"}
        assert "error" not in response

    def test_build_error_response(self) -> None:
        """Test building error response."""
        error = ServerError("Something went wrong")
        response = build_error_response(request_id=1, error=error)

        assert response["jsonrpc"] == "2.0"
        assert response["id"] == 1
        assert "result" not in response
        assert response["error"]["code"] == SERVER_ERROR
        assert response["error"]["message"] == "Something went wrong"

    def test_error_response_includes_correlation_id(self) -> None:
        """Test error response includes correlation ID."""
        error = ServerError("Server error")
        response = build_error_response(
            request_id=1, error=error, correlation_id="test-correlation-123"
        )

        assert response["error"]["data"]["correlation_id"] == "test-correlation-123"

    def test_error_response_format(self) -> None:
        """Test error response has correct structure."""
        error = InvalidParamsError("Bad parameters")
        response = build_error_response(request_id=42, error=error)

        assert response["error"]["code"] == INVALID_PARAMS
        assert response["error"]["message"] == "Bad parameters"
        assert "data" in response["error"]

    def test_method_not_found_error(self) -> None:
        """Test MethodNotFound error."""
        error = MethodNotFoundError("unknown_method")
        response = build_error_response(request_id=1, error=error)

        assert response["error"]["code"] == METHOD_NOT_FOUND
        assert "unknown_method" in response["error"]["message"]


class TestSerializeResponse:
    """Test JSON-RPC response serialization."""

    def test_serialize_success_response(self) -> None:
        """Test serializing success response."""
        response = build_response(request_id=1, result={"test": "data"})
        serialized = serialize_response(response)

        # Should be valid JSON
        parsed = json.loads(serialized)
        assert parsed["jsonrpc"] == "2.0"
        assert parsed["id"] == 1
        assert parsed["result"]["test"] == "data"

    def test_serialize_error_response(self) -> None:
        """Test serializing error response."""
        error = ParseError()
        response = build_error_response(request_id=None, error=error)
        serialized = serialize_response(response)

        parsed = json.loads(serialized)
        assert parsed["error"]["code"] == PARSE_ERROR
