from __future__ import annotations

"""
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/>.
"""

import json

import pytest

from rabbitmq_mcp_connection.tools.get_id import (
    OPERATION_ID_PATTERN,
    OperationNotFoundError,
    _find_similar_operations,
    _generate_usage_hints,
    get_operation_schema,
    handle_get_id,
)
from tools import get_id

# NOTE: These tests are for the old tools.get_id implementation that loads from OpenAPI YAML files.
# The new implementation uses data/operations.json registry (tested below).
# Skipping these tests as they require OpenAPI spec files that don't exist in the current structure.


@pytest.mark.skip(reason="Old implementation - replaced by operations.json registry")
def test_get_id_returns_operation_schema() -> None:
    schema = get_id.get_id("queues.list")

    assert schema.operation_id == "queues.list"
    assert schema.summary
    assert schema.description
    assert isinstance(schema.parameters, list)
    assert "200" in schema.responses


@pytest.mark.skip(reason="Old implementation - replaced by operations.json registry")
def test_get_id_raises_for_unknown_operation() -> None:
    with pytest.raises(ValueError) as exc:
        get_id.get_id("unknown.operation")

    assert "unknown.operation" in str(exc.value)


# Tests for enhanced get_id functionality from rabbitmq_mcp_connection


@pytest.mark.asyncio
async def test_handle_get_id_success() -> None:
    """Test successful operation lookup returns schema."""
    result = await handle_get_id("queues.list", correlation_id="test-123")

    assert result.success is True
    assert result.error is None
    assert "schema" in result.result
    assert "formatted" in result.result
    assert result.result["schema"]["operation_id"] == "queues.list"


@pytest.mark.asyncio
async def test_handle_get_id_not_found() -> None:
    """Test operation not found returns error with suggestions."""
    result = await handle_get_id("invalid.operation", correlation_id="test-456")

    assert result.success is False
    assert result.error is not None
    assert result.error.code == "OPERATION_NOT_FOUND"
    assert "invalid.operation" in result.error.message
    assert (
        "search-ids" in result.error.message or "Did you mean" in result.error.message
    )


@pytest.mark.asyncio
async def test_handle_get_id_formatted_output() -> None:
    """Test response includes pretty-printed JSON."""
    result = await handle_get_id("queues.list")

    assert result.success is True
    formatted = result.result["formatted"]

    # Verify it's valid JSON
    parsed = json.loads(formatted)
    assert parsed["operation_id"] == "queues.list"

    # Verify it's pretty-printed (has indentation)
    assert "  " in formatted or "\n" in formatted


def test_find_similar_operations() -> None:
    """Test fuzzy matching finds similar operation IDs."""
    operations = {
        "queues.list": {},
        "queues.get": {},
        "queues.create": {},
        "exchanges.list": {},
    }

    # Test namespace match
    similar = _find_similar_operations("queues.lst", operations)
    assert "queues.list" in similar

    # Test partial match
    similar = _find_similar_operations("queue.list", operations)
    assert len(similar) > 0


def test_find_similar_operations_no_match() -> None:
    """Test fuzzy matching returns empty list when no matches."""
    operations = {
        "queues.list": {},
        "exchanges.list": {},
    }

    similar = _find_similar_operations("completely.different", operations)
    # May return empty or very weak matches depending on cutoff
    assert isinstance(similar, list)


def test_generate_usage_hints_authentication() -> None:
    """Test usage hints for authentication requirement."""
    operation_data = {"requires_auth": True}
    hints = _generate_usage_hints(operation_data)

    assert any("authentication" in hint.lower() for hint in hints)


def test_generate_usage_hints_modification() -> None:
    """Test usage hints for resource modification."""
    for method in ["POST", "PUT", "PATCH", "DELETE"]:
        operation_data = {"http_method": method}
        hints = _generate_usage_hints(operation_data)

        assert any("modifies resources" in hint for hint in hints)


def test_generate_usage_hints_deprecated() -> None:
    """Test usage hints for deprecated operations."""
    operation_data = {"deprecated": True, "alternative_operation": "queues.v2.list"}
    hints = _generate_usage_hints(operation_data)

    assert any("deprecated" in hint.lower() for hint in hints)
    assert any("queues.v2.list" in hint for hint in hints)


def test_generate_usage_hints_rate_limit() -> None:
    """Test usage hints for rate-limited operations."""
    operation_data = {"rate_limit_exempt": False}
    hints = _generate_usage_hints(operation_data)

    assert any("rate-limited" in hint.lower() for hint in hints)


def test_generate_usage_hints_empty() -> None:
    """Test no hints for safe, non-rate-limited GET operations."""
    operation_data = {
        "requires_auth": False,
        "method": "GET",
        "deprecated": False,
        "rate_limit_exempt": True,
    }
    hints = _generate_usage_hints(operation_data)

    # Should have no hints (safe operation)
    assert len(hints) == 0 or hints == []


def test_get_operation_schema_raises_not_found() -> None:
    """Test get_operation_schema raises OperationNotFoundError."""
    with pytest.raises(OperationNotFoundError):
        get_operation_schema("nonexistent.operation")


@pytest.mark.asyncio
async def test_handle_get_id_with_correlation_id() -> None:
    """Test correlation_id is logged (check no exceptions raised)."""
    result = await handle_get_id("queues.list", correlation_id="test-correlation-123")
    assert result.success is True


@pytest.mark.asyncio
async def test_handle_get_id_caching() -> None:
    """Test response caching works (repeated calls should use cache)."""
    # First call
    result1 = await handle_get_id("queues.list")
    formatted1 = result1.result["formatted"]

    # Second call (should use cache)
    result2 = await handle_get_id("queues.list")
    formatted2 = result2.result["formatted"]

    # Should return identical formatted output
    assert formatted1 == formatted2


@pytest.mark.asyncio
async def test_handle_get_id_empty_operation_id() -> None:
    """Test empty operation_id returns validation error."""
    result = await handle_get_id("", correlation_id="test-empty")

    assert result.success is False
    assert result.error is not None
    assert result.error.code == "INVALID_PARAMETER"
    assert "non-empty" in result.error.message.lower()


@pytest.mark.asyncio
async def test_handle_get_id_malformed_operation_id() -> None:
    """Test malformed operation_id (missing namespace) returns validation error."""
    result = await handle_get_id("invalidoperation", correlation_id="test-malformed")

    assert result.success is False
    assert result.error is not None
    assert result.error.code == "INVALID_PARAMETER"
    assert "pattern" in result.error.message.lower()


def test_operation_id_pattern_constant() -> None:
    """Test OPERATION_ID_PATTERN constant is defined and valid."""
    import re

    assert OPERATION_ID_PATTERN is not None
    # Should match valid operation IDs
    assert re.match(OPERATION_ID_PATTERN, "queues.list")
    assert re.match(OPERATION_ID_PATTERN, "exchanges.delete_by_params")
    # Should not match invalid IDs
    assert not re.match(OPERATION_ID_PATTERN, "invalid")
    assert not re.match(OPERATION_ID_PATTERN, "")
    assert not re.match(OPERATION_ID_PATTERN, "UPPERCASE.ACTION")
