"""Tests for resolve-element MCP tool.

The resolution logic lives in `adapters/sec/mcp/element_resolver.py`.
These tests exercise the tool through ``execute()`` and patch the
ops-layer enricher via ``_get_enricher`` at the module level.
"""

from unittest.mock import AsyncMock, Mock, patch

import pytest

from robosystems.middleware.mcp.tools.resolve_element_tool import ResolveElementTool

_RESOLVER = "robosystems.adapters.sec.mcp.element_resolver"


@pytest.fixture
def mock_client():
  client = AsyncMock()
  client.graph_id = "sec"
  return client


def _make_mock_enricher(canonical_concept=None):
  """Create a Mock enricher (sync methods, not async)."""
  enricher = Mock()
  enricher.embed_batch.return_value = [[0.1] * 384]
  enricher.match_canonical_from_query.return_value = canonical_concept
  return enricher


def _patch_enricher(enricher):
  """Patch the ops-layer ``_get_enricher`` helper to return ``enricher``."""
  return patch(f"{_RESOLVER}._get_enricher", return_value=enricher)


def _query_router(**responses):
  """Create a mock execute_query that returns different results based on query content.

  Key order matters — more specific patterns checked first.
  """
  defaults = {
    "CONTAINS": [],  # Text fallback (most specific, checked first)
    "canonical_concept = $canonical_id": [],  # Canonical WHERE clause (not RETURN clause)
    "ELEMENT_HAS_LABEL": [],
    "FACT_HAS_ELEMENT": [],
  }
  defaults.update(responses)

  async def route(query, **kwargs):
    for key, result in defaults.items():
      if key in query:
        return result
    return []

  return route


@pytest.fixture
def tool(mock_client):
  return ResolveElementTool(mock_client)


class TestResolveElementToolDefinition:
  def test_tool_definition_structure(self, tool):
    defn = tool.get_tool_definition()
    assert defn["name"] == "resolve-element"
    assert "concept" in defn["inputSchema"]["properties"]
    assert "ticker" in defn["inputSchema"]["properties"]
    assert "report_id" in defn["inputSchema"]["properties"]
    assert defn["inputSchema"]["required"] == ["concept"]


class TestResolveElementExecution:
  @pytest.mark.asyncio
  async def test_empty_concept(self, tool):
    result = await tool.execute({"concept": ""})
    assert "error" in result

  @pytest.mark.asyncio
  async def test_with_canonical_match(self, mock_client):
    """When taxonomy matches, result includes canonical info and graph query results."""
    from robosystems.adapters.sec.taxonomy.concepts import CanonicalConcept

    mock_concept = CanonicalConcept(
      id="revenue",
      display_name="Revenue",
      category="income_statement",
      description="Total revenue",
      embedding=[0.1] * 384,
    )

    tool = ResolveElementTool(mock_client)

    mock_client.execute_query = _query_router(
      **{
        "canonical_concept = $canonical_id": [
          {
            "qname": "us-gaap:Revenues",
            "confidence": 0.95,
            "fact_count": 100,
          }
        ],
        "ELEMENT_HAS_LABEL": [{"qname": "us-gaap:Revenues", "label": "Revenue"}],
      }
    )

    with _patch_enricher(_make_mock_enricher(canonical_concept=mock_concept)):
      result = await tool.execute({"concept": "revenue"})
    assert result["canonical_id"] == "revenue"
    assert result["canonical_name"] == "Revenue"
    assert len(result["matches"]) >= 1
    assert result["matches"][0]["qname"] == "us-gaap:Revenues"
    assert result["matches"][0]["fact_count"] == 100
    assert result["matches"][0]["label"] == "Revenue"
    assert result["query_hint"] is not None

  @pytest.mark.asyncio
  async def test_with_ticker_filter(self, mock_client):
    from robosystems.adapters.sec.taxonomy.concepts import CanonicalConcept

    mock_concept = CanonicalConcept(
      id="revenue",
      display_name="Revenue",
      category="income_statement",
      description="Total revenue",
      embedding=[0.1] * 384,
    )

    tool = ResolveElementTool(mock_client)

    mock_client.execute_query = _query_router(
      **{
        "canonical_concept = $canonical_id": [
          {
            "qname": "us-gaap:Revenues",
            "confidence": 0.95,
            "fact_count": 50,
          }
        ],
        "ELEMENT_HAS_LABEL": [{"qname": "us-gaap:Revenues", "label": "Revenue"}],
      }
    )

    with _patch_enricher(_make_mock_enricher(canonical_concept=mock_concept)):
      result = await tool.execute({"concept": "revenue", "ticker": "NVDA"})
    assert result["ticker"] == "NVDA"
    assert "$ticker" in result["query_hint"]
    assert result["query_hint_params"]["ticker"] == "NVDA"

  @pytest.mark.asyncio
  async def test_with_report_id_filter(self, mock_client):
    from robosystems.adapters.sec.taxonomy.concepts import CanonicalConcept

    mock_concept = CanonicalConcept(
      id="revenue",
      display_name="Revenue",
      category="income_statement",
      description="Total revenue",
      embedding=[0.1] * 384,
    )

    tool = ResolveElementTool(mock_client)

    mock_client.execute_query = _query_router(
      **{
        "canonical_concept = $canonical_id": [
          {
            "qname": "us-gaap:Revenues",
            "confidence": 0.95,
            "fact_count": 12,
          }
        ],
        "ELEMENT_HAS_LABEL": [{"qname": "us-gaap:Revenues", "label": "Revenue"}],
      }
    )

    with _patch_enricher(_make_mock_enricher(canonical_concept=mock_concept)):
      result = await tool.execute(
        {"concept": "revenue", "report_id": "0001045810-25-000023"}
      )
    assert result["report_id"] == "0001045810-25-000023"
    assert "$report_id" in result["query_hint"]
    assert result["query_hint_params"]["report_id"] == "0001045810-25-000023"
    assert "REPORT_HAS_FACT" in result["query_hint"]
    assert result["matches"][0]["fact_count"] == 12

  @pytest.mark.asyncio
  async def test_no_canonical_match_falls_back_to_text(self, mock_client):
    """When no canonical match, falls back to text search on labels."""
    tool = ResolveElementTool(mock_client)

    mock_client.execute_query = _query_router(
      **{
        "CONTAINS": [
          {
            "qname": "us-gaap:SomeObscureMetric",
            "label": "Some Obscure Metric",
            "concept": None,
            "confidence": None,
            "fact_count": 5,
          }
        ],
      }
    )

    with _patch_enricher(_make_mock_enricher()):  # No canonical match
      result = await tool.execute({"concept": "some obscure metric"})
    assert result["canonical_id"] is None
    assert len(result["matches"]) >= 1
    assert result["matches"][0]["qname"] == "us-gaap:SomeObscureMetric"

  @pytest.mark.asyncio
  async def test_no_matches_at_all(self, mock_client):
    tool = ResolveElementTool(mock_client)

    mock_client.execute_query = _query_router()

    with _patch_enricher(_make_mock_enricher()):
      result = await tool.execute({"concept": "nonexistent metric"})
    assert result["canonical_id"] is None
    assert len(result["matches"]) == 0
    assert result["query_hint"] is None

  @pytest.mark.asyncio
  async def test_query_hint_generated(self, mock_client):
    from robosystems.adapters.sec.taxonomy.concepts import CanonicalConcept

    mock_concept = CanonicalConcept(
      id="total_assets",
      display_name="Total Assets",
      category="balance_sheet",
      description="Total assets",
      embedding=[0.1] * 384,
    )

    tool = ResolveElementTool(mock_client)

    mock_client.execute_query = _query_router(
      **{
        "canonical_concept = $canonical_id": [
          {"qname": "us-gaap:Assets", "confidence": 0.95, "fact_count": 200}
        ],
        "ELEMENT_HAS_LABEL": [{"qname": "us-gaap:Assets", "label": "Assets"}],
      }
    )

    with _patch_enricher(_make_mock_enricher(canonical_concept=mock_concept)):
      result = await tool.execute({"concept": "total assets"})
    assert result["query_hint"] is not None
    assert "$qname" in result["query_hint"]
    assert result["query_hint_params"]["qname"] == "us-gaap:Assets"
    assert "has_dimensions = false" in result["query_hint"]

  @pytest.mark.asyncio
  async def test_canonical_lookup_with_labels(self, mock_client):
    """Canonical lookup enriches results with labels."""
    from robosystems.adapters.sec.taxonomy.concepts import CanonicalConcept

    mock_concept = CanonicalConcept(
      id="revenue",
      display_name="Revenue",
      category="income_statement",
      description="Total revenue",
      embedding=[0.1] * 384,
    )

    tool = ResolveElementTool(mock_client)

    mock_client.execute_query = _query_router(
      **{
        "canonical_concept = $canonical_id": [
          {"qname": "us-gaap:Revenues", "confidence": 0.95, "fact_count": 100}
        ],
        "ELEMENT_HAS_LABEL": [{"qname": "us-gaap:Revenues", "label": "Revenue"}],
      }
    )

    with _patch_enricher(_make_mock_enricher(canonical_concept=mock_concept)):
      result = await tool.execute({"concept": "revenue"})
    assert result["canonical_id"] == "revenue"
    assert len(result["matches"]) >= 1
    assert result["matches"][0]["qname"] == "us-gaap:Revenues"
