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

"""Integration tests for search_ids semantic quality and performance."""

import json
import time
from pathlib import Path

import pytest

from tools.search_ids import search_ids


@pytest.fixture
def real_embeddings() -> dict:
    """Load real embeddings from data/embeddings.json."""
    embeddings_path = Path("data/embeddings.json")
    if not embeddings_path.exists():
        pytest.skip("data/embeddings.json not found")

    with embeddings_path.open("r") as f:
        return json.load(f)


@pytest.fixture
def real_operations() -> dict:
    """Load real operations from data/operations.json."""
    operations_path = Path("data/operations.json")
    if not operations_path.exists():
        pytest.skip("data/operations.json not found")

    with operations_path.open("r") as f:
        return json.load(f)


def test_search_quality_list_queues(real_embeddings, real_operations):
    """Test AC#2: Search returns relevant results for queue query."""
    result = search_ids(
        "list all queues",
        threshold=0.1,  # Realistic threshold for this model
        max_results=20,  # Get more results to find queues
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # Should return results
    assert len(result.results) > 0

    # Results should be sorted by score
    scores = [r.similarity_score for r in result.results]
    assert scores == sorted(
        scores, reverse=True
    ), "Results should be sorted by similarity descending"

    # Results should have all required fields
    for r in result.results:
        assert hasattr(r, "operation_id")
        assert hasattr(r, "description")
        assert hasattr(r, "similarity_score")
        assert hasattr(r, "namespace")


def test_search_quality_create_exchange(real_embeddings, real_operations):
    """Test semantic quality: Query 'create exchange' returns exchange.create operations."""
    result = search_ids(
        "create exchange",
        threshold=0.1,  # Realistic threshold
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # Should return results
    assert len(result.results) > 0

    # Should contain exchange-related operations
    exchange_results = [
        r
        for r in result.results
        if "exchange" in r.operation_id.lower() or "exchange" in r.description.lower()
    ]
    assert len(exchange_results) > 0


def test_search_quality_bindings(real_embeddings, real_operations):
    """Test semantic quality: Query 'message bindings' returns binding operations."""
    result = search_ids(
        "message bindings",
        threshold=0.1,  # Realistic threshold
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # Should return results
    assert len(result.results) > 0

    # Should contain binding-related operations
    binding_operations = [
        r
        for r in result.results
        if "binding" in r.operation_id.lower() or "binding" in r.description.lower()
    ]
    assert len(binding_operations) > 0


def test_search_performance_latency(real_embeddings, real_operations):
    """Test AC#4: Search completes in <100ms (p95 latency)."""
    # Warm up cache
    search_ids(
        "test query",
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # Measure latency for multiple queries
    latencies = []
    test_queries = [
        "list queues",
        "create exchange",
        "delete binding",
        "get overview",
        "show connections",
    ]

    for query in test_queries * 20:  # 100 total queries
        start_time = time.time()
        search_ids(
            query,
            embeddings=real_embeddings,
            operations_registry=real_operations,
        )
        latency_ms = (time.time() - start_time) * 1000
        latencies.append(latency_ms)

    # Calculate p95 latency
    latencies.sort()
    p95_index = int(len(latencies) * 0.95)
    p95_latency = latencies[p95_index]

    # Assert p95 latency < 100ms
    assert p95_latency < 100, f"P95 latency {p95_latency:.2f}ms exceeds 100ms"


def test_search_cache_effectiveness(real_embeddings, real_operations):
    """Test AC#4: Cache makes repeat queries consistent."""
    from tools.search_ids import _get_cached_query_embedding

    _get_cached_query_embedding.cache_clear()

    # First query (cache miss)
    result1 = search_ids(
        "list all queues",
        threshold=0.1,
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # Second query with same input (cache hit)
    result2 = search_ids(
        "list all queues",
        threshold=0.1,
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # Results should be identical
    assert len(result1.results) == len(result2.results)
    if len(result1.results) > 0:
        assert result1.results[0].operation_id == result2.results[0].operation_id
        assert (
            abs(
                result1.results[0].similarity_score
                - result2.results[0].similarity_score
            )
            < 0.0001
        )


def test_search_cross_namespace(real_embeddings, real_operations):
    """Test semantic quality: Cross-namespace queries return diverse operations."""
    result = search_ids(
        "show all resources",
        threshold=0.1,  # Lower threshold for cross-namespace
        max_results=20,
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # Should return results from multiple namespaces
    assert len(result.results) > 0
    namespaces = {r.namespace for r in result.results if r.namespace}
    # With lower threshold, should get diverse results
    assert (
        len(namespaces) >= 1
    ), f"Expected diverse operations, got namespaces: {namespaces}"


def test_search_zero_results_low_threshold(real_embeddings, real_operations):
    """Test handling of queries with no matches above threshold."""
    result = search_ids(
        "xyz nonsense query abc",
        threshold=0.95,  # Very high threshold to ensure no matches
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # May return empty results or very few results
    assert isinstance(result.results, list)


def test_search_with_132_operations(real_embeddings, real_operations):
    """Test performance with full 132 operations dataset."""
    # Verify we have expected number of operations
    embeddings_dict = real_embeddings.get("embeddings", {})
    assert len(embeddings_dict) >= 100, "Should have significant number of operations"

    # Warm up: load embedding model and cache a query
    search_ids(
        "warmup query",
        threshold=0.1,
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )

    # Run actual search test
    start_time = time.time()
    result = search_ids(
        "list queues",
        threshold=0.1,  # Use realistic threshold
        embeddings=real_embeddings,
        operations_registry=real_operations,
    )
    execution_time_ms = (time.time() - start_time) * 1000

    # Should complete quickly even with many operations (excluding model loading)
    assert (
        execution_time_ms < 100
    ), f"Search took {execution_time_ms:.2f}ms, expected <100ms"
    assert len(result.results) > 0
