#!/usr/bin/env python3
"""
Integration tests for chat geometry routing.

WO-ECHO-FIBPI3D-LOOP-02: Verify /v1/chat/completions routes geometry to spatial tool.

Run with:
    pytest tests/test_chat_geometry_routing.py -v

Requires basin_server to be running:
    BASIN_BACKEND=llama_cpp uvicorn api.basin_server:app --port 8001
"""

import json
import os
import sys
from pathlib import Path
from typing import Any, Dict
from unittest.mock import patch

import pytest

# Add project root for imports
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

from helix_cdc.echo.ops.helpers import (
    detect_geometry_intent,
    detect_math_intent,
    detect_retrieval_intent,
    detect_code_intent,
)
from helix_cdc.echo.ops.transport_policy import (
    TransportMode,
    TRANSPORT_POLICIES,
    ROUTER_SCHEMA_VERSION,
    get_policy,
    get_tool_routed_policies,
)
from helix_cdc.echo.ops.models import ROUTER_SCHEMA_VERSION as MODELS_ROUTER_SCHEMA_VERSION


# =============================================================================
# Unit Tests for Geometry Detection
# =============================================================================

class TestGeometryDetection:
    """Unit tests for detect_geometry_intent helper."""

    def test_distance_query_detected(self):
        """Distance queries should be detected as geometry."""
        result = detect_geometry_intent("What is the distance between obj_0000 and obj_0001?")
        assert result["is_geometry"] is True
        assert "distance" in result["matched_keywords"]
        assert result["confidence"] > 0.0

    def test_nearest_query_detected(self):
        """Nearest neighbor queries should be detected as geometry."""
        result = detect_geometry_intent("Find the 5 nearest objects to obj_0010")
        assert result["is_geometry"] is True
        assert "nearest" in result["matched_keywords"]

    def test_position_query_detected(self):
        """Position queries should be detected as geometry."""
        result = detect_geometry_intent("Is obj_0000 to the left of obj_0001?")
        assert result["is_geometry"] is True
        assert "left of" in result["matched_keywords"]

    def test_non_geometry_not_detected(self):
        """Non-geometry prompts should not be detected as geometry."""
        result = detect_geometry_intent("What is the capital of France?")
        assert result["is_geometry"] is False
        assert result["confidence"] == 0.0

    def test_pattern_match_high_confidence(self):
        """Pattern matches should boost confidence."""
        result = detect_geometry_intent("What is the distance between obj_0000 and obj_0001?")
        assert result["is_geometry"] is True
        # Should match "distance between" pattern and obj_XXXX pattern
        assert len(result["matched_patterns"]) >= 1
        assert result["confidence"] > 0.25

    def test_object_pattern_detection(self):
        """obj_XXXX pattern should be detected."""
        result = detect_geometry_intent("Find information about obj_0042")
        assert result["is_geometry"] is True
        assert any("obj_" in p for p in result["matched_patterns"])


# =============================================================================
# Unit Tests for Math Detection (WO-MODULAR-INVENTORY-01)
# =============================================================================

class TestMathDetection:
    """Unit tests for detect_math_intent helper."""

    def test_arithmetic_expression_detected(self):
        """Arithmetic expressions should be detected as math."""
        result = detect_math_intent("What is 5 + 3?")
        assert result["is_math"] is True
        assert result["confidence"] > 0.0

    def test_calculate_keyword_detected(self):
        """Calculate keyword should be detected."""
        result = detect_math_intent("Calculate the sum of 10 and 20")
        assert result["is_math"] is True
        assert "calculate" in result["matched_keywords"]

    def test_percentage_pattern_detected(self):
        """Percentage patterns should be detected."""
        result = detect_math_intent("What is 15% of 200?")
        assert result["is_math"] is True
        assert len(result["matched_patterns"]) >= 1

    def test_sqrt_detected(self):
        """Square root should be detected."""
        result = detect_math_intent("What is sqrt(16)?")
        assert result["is_math"] is True

    def test_non_math_not_detected(self):
        """Non-math prompts should not be detected."""
        result = detect_math_intent("Tell me about the capital of France")
        assert result["is_math"] is False

    # WO-TOOL-ROUTING-HARDEN-01: Tests for false positive rejection
    def test_what_is_question_not_math(self):
        """'What is' questions without numbers should not trigger math."""
        result = detect_math_intent("What is the capital of France?")
        assert result["is_math"] is False, "Should not trigger on 'what is' without numbers"

    def test_log_file_not_math(self):
        """'Log file' should not trigger math detection."""
        result = detect_math_intent("Check the log file for errors")
        assert result["is_math"] is False, "'log' in 'log file' should not trigger math"

    def test_how_much_is_without_number_not_math(self):
        """'How much is' without numbers should not trigger math."""
        result = detect_math_intent("How much is the membership fee?")
        assert result["is_math"] is False, "Price query should not trigger math"

    def test_equals_in_english_not_math(self):
        """'equals' in English prose should not trigger math."""
        result = detect_math_intent("This approach equals success in most cases")
        assert result["is_math"] is False, "'equals' in prose should not trigger math"

    def test_keywords_without_digits_rejected(self):
        """Keywords alone without digits should require 3+ matches."""
        # Single keyword without digits
        result = detect_math_intent("Please calculate this for me")
        # "calculate" alone without digits should not trigger
        # (unless other keywords present)
        assert result["has_digits"] is False

    def test_multiplication_pattern(self):
        """Multiplication patterns should be detected."""
        result = detect_math_intent("What is 7 * 8?")
        assert result["is_math"] is True
        assert result["confidence"] > 0.25


# =============================================================================
# Unit Tests for Retrieval Detection (WO-MODULAR-INVENTORY-01)
# =============================================================================

class TestRetrievalDetection:
    """Unit tests for detect_retrieval_intent helper."""

    def test_search_keyword_detected(self):
        """Search keyword should be detected."""
        result = detect_retrieval_intent("Search for documents about machine learning")
        assert result["is_retrieval"] is True
        assert "search" in result["matched_keywords"]

    def test_look_up_pattern_detected(self):
        """Look up pattern should be detected."""
        result = detect_retrieval_intent("Look up information about Python programming")
        assert result["is_retrieval"] is True
        assert len(result["matched_patterns"]) >= 1

    def test_documentation_keyword_detected(self):
        """Documentation keywords should be detected."""
        result = detect_retrieval_intent("Find this in the documentation")
        assert result["is_retrieval"] is True
        assert any(kw in result["matched_keywords"] for kw in ["documentation", "find"])

    def test_definition_pattern_detected(self):
        """Definition patterns should be detected."""
        result = detect_retrieval_intent("What is the definition of entropy?")
        assert result["is_retrieval"] is True

    def test_non_retrieval_not_detected(self):
        """Non-retrieval prompts should not be detected."""
        result = detect_retrieval_intent("Calculate 5 + 3")
        assert result["is_retrieval"] is False


# =============================================================================
# Unit Tests for Code Detection (WO-MODULAR-INVENTORY-01)
# =============================================================================

class TestCodeDetection:
    """Unit tests for detect_code_intent helper."""

    def test_run_code_detected(self):
        """Run code keywords should be detected."""
        result = detect_code_intent("Run this Python code for me")
        assert result["is_code"] is True
        # WO-TOOL-ROUTING-HARDEN-01: Changed from "run" to "run this" or "run code"
        assert "python" in result["matched_keywords"]
        assert any(kw in result["matched_keywords"] for kw in ["run this", "run code", "code"])

    def test_execute_keyword_detected(self):
        """Execute keyword should be detected."""
        # WO-TOOL-ROUTING-HARDEN-01: Need more context for execute to trigger
        result = detect_code_intent("Execute this Python script please")
        assert result["is_code"] is True
        # Now needs "script" + "python" keywords or similar
        assert any(kw in result["matched_keywords"] for kw in ["script", "python", "execute script"])

    def test_code_block_pattern_detected(self):
        """Code block patterns should be detected."""
        result = detect_code_intent("```python\nprint('hello')\n```")
        assert result["is_code"] is True
        assert len(result["matched_patterns"]) >= 1

    def test_import_pattern_detected(self):
        """Import statement pattern should be detected."""
        result = detect_code_intent("import numpy as np")
        assert result["is_code"] is True

    def test_function_definition_detected(self):
        """Function definitions should be detected."""
        result = detect_code_intent("def hello_world():")
        assert result["is_code"] is True

    def test_non_code_not_detected(self):
        """Non-code prompts should not be detected."""
        result = detect_code_intent("What is the capital of France?")
        assert result["is_code"] is False

    # WO-TOOL-ROUTING-HARDEN-01: Tests for false positive rejection
    def test_run_alone_not_code(self):
        """Standalone 'run' should not trigger code detection."""
        result = detect_code_intent("Let's run to the store")
        assert result["is_code"] is False, "'run' alone should not trigger code"

    def test_execute_alone_not_code(self):
        """Standalone 'execute' should not trigger code detection."""
        result = detect_code_intent("Execute the plan tomorrow")
        assert result["is_code"] is False, "'execute' alone should not trigger code"

    def test_command_alone_not_code(self):
        """Standalone 'command' should not trigger code detection."""
        # Removed "command" from keywords since it was too broad
        result = detect_code_intent("He has command of the situation")
        assert result["is_code"] is False, "'command' alone should not trigger code"


# =============================================================================
# Unit Tests for Transport Policy Registry (WO-MODULAR-INVENTORY-01)
# =============================================================================

class TestTransportPolicyRegistry:
    """Unit tests for transport policy registry."""

    def test_geometry_policy_exists(self):
        """Geometry routing policy should exist."""
        policy = get_policy(TransportMode.GEOMETRY_ROUTED)
        assert policy.tool_name == "fibpi3d_spatial_tool"
        assert policy.requires_llm is False

    def test_math_policy_exists(self):
        """Math routing policy should exist."""
        policy = get_policy(TransportMode.MATH_ROUTED)
        assert policy.tool_name == "calculator_tool"
        assert policy.requires_llm is False

    def test_retrieval_policy_exists(self):
        """Retrieval routing policy should exist."""
        policy = get_policy(TransportMode.RETRIEVAL_ROUTED)
        assert policy.tool_name == "retrieval_tool"
        assert policy.requires_llm is False

    def test_code_policy_exists(self):
        """Code routing policy should exist."""
        policy = get_policy(TransportMode.CODE_ROUTED)
        assert policy.tool_name == "code_executor_tool"
        assert policy.requires_llm is False

    def test_llm_policy_requires_llm(self):
        """LLM routing policy should require LLM."""
        policy = get_policy(TransportMode.LLM_ROUTED)
        assert policy.tool_name is None
        assert policy.requires_llm is True

    def test_tool_routed_policies_sorted_by_priority(self):
        """Tool-routed policies should be sorted by priority."""
        policies = get_tool_routed_policies()
        assert len(policies) >= 4
        priorities = [p.priority for p in policies]
        assert priorities == sorted(priorities)

    def test_router_schema_version_consistent(self):
        """Router schema version should be consistent across modules."""
        assert ROUTER_SCHEMA_VERSION == MODELS_ROUTER_SCHEMA_VERSION
        assert ROUTER_SCHEMA_VERSION == "1.0.0"


# =============================================================================
# Integration Tests (require server running)
# =============================================================================

# Check if server is available
def is_server_running(host: str = "127.0.0.1", port: int = 8001) -> bool:
    """Check if basin_server is running."""
    import socket
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(1)
            s.connect((host, port))
            return True
    except (socket.timeout, ConnectionRefusedError, OSError):
        return False


# Skip integration tests if server not running
requires_server = pytest.mark.skipif(
    not is_server_running(),
    reason="basin_server not running on port 8001"
)


def make_chat_request(prompt: str, endpoint: str = "http://127.0.0.1:8001") -> Dict[str, Any]:
    """Make a chat completion request to basin_server."""
    import requests

    response = requests.post(
        f"{endpoint}/v1/chat/completions",
        json={
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.0,
            "top_p": 1.0,
            "max_tokens": 256,
            "quant": {
                "phi0_deg": 0.0,
                "cardinality": 2,
                "control_consent": True,
            },
        },
        timeout=30,
    )
    return response.json() if response.ok else {"error": response.text, "status_code": response.status_code}


@requires_server
class TestChatGeometryRouting:
    """Integration tests for chat geometry routing."""

    def test_geometry_prompt_routed_to_spatial(self):
        """Geometry prompts should be routed to spatial tool."""
        data = make_chat_request("What is the distance between obj_0000 and obj_0001?")

        assert "error" not in data or "status_code" not in data
        assert "choices" in data
        assert len(data["choices"]) > 0

        choice = data["choices"][0]
        assert "basin_metadata" in choice

        metadata = choice["basin_metadata"]
        assert metadata.get("transport_mode") == "geometry_routed"

    def test_geometry_response_has_router_decision(self):
        """Geometry responses should include router decision."""
        data = make_chat_request("Find the 5 nearest objects to obj_0010")

        assert "choices" in data
        choice = data["choices"][0]
        metadata = choice.get("basin_metadata", {})

        if metadata.get("transport_mode") == "geometry_routed":
            assert "router_decision" in metadata
            router = metadata["router_decision"]
            assert router["is_geometry"] is True

    def test_geometry_response_has_loop_receipt(self):
        """Geometry responses should include loop receipt."""
        data = make_chat_request("What is the distance between obj_0000 and obj_0001?")

        assert "choices" in data
        metadata = data["choices"][0].get("basin_metadata", {})

        if metadata.get("transport_mode") == "geometry_routed":
            assert "streaming_receipts" in data
            receipts = data["streaming_receipts"]
            assert len(receipts) > 0

            receipt = receipts[0]
            assert "loop_id" in receipt
            assert "chain_hash" in receipt
            assert "work_order" in receipt
            assert receipt["work_order"] == "WO-ECHO-FIBPI3D-LOOP-02"

    def test_geometry_response_has_chain_hash(self):
        """Geometry responses should have valid chain hash in metadata."""
        data = make_chat_request("What is the distance between obj_0000 and obj_0001?")

        metadata = data["choices"][0].get("basin_metadata", {})

        if metadata.get("transport_mode") == "geometry_routed":
            assert "chain_hash" in metadata
            # Chain hash should be 64 hex characters (SHA256)
            assert len(metadata["chain_hash"]) == 64

    def test_non_geometry_not_routed(self):
        """Non-geometry prompts should not be routed to spatial tool."""
        data = make_chat_request("What is the capital of France?")

        assert "choices" in data
        choice = data["choices"][0]
        metadata = choice.get("basin_metadata", {})

        # Should NOT be geometry_routed
        assert metadata.get("transport_mode") != "geometry_routed"

    def test_geometry_answer_is_meaningful(self):
        """Geometry answers should contain meaningful spatial information."""
        data = make_chat_request("What is the distance between obj_0000 and obj_0001?")

        assert "choices" in data
        content = data["choices"][0]["message"]["content"]

        metadata = data["choices"][0].get("basin_metadata", {})
        if metadata.get("transport_mode") == "geometry_routed":
            # Answer should mention distance
            assert "distance" in content.lower() or "units" in content.lower()

    def test_k_nearest_returns_neighbors(self):
        """k_nearest queries should return neighbor list."""
        data = make_chat_request("Find the 5 nearest objects to obj_0000")

        assert "choices" in data
        content = data["choices"][0]["message"]["content"]

        metadata = data["choices"][0].get("basin_metadata", {})
        if metadata.get("transport_mode") == "geometry_routed":
            # Answer should mention neighbors
            assert "nearest" in content.lower() or "obj_" in content


# =============================================================================
# FastAPI TestClient Tests (no server required)
# =============================================================================

class TestChatGeometryRoutingTestClient:
    """
    Tests using FastAPI TestClient (no external server required).

    Run with: BASIN_BACKEND=mock pytest tests/test_chat_geometry_routing.py::TestChatGeometryRoutingTestClient -v
    """

    @pytest.fixture
    def client(self):
        """Create TestClient for basin_server."""
        from fastapi.testclient import TestClient
        from api.basin_server import app
        return TestClient(app)

    def test_geometry_endpoint_works(self, client):
        """Verify geometry routing works via TestClient."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "What is the distance between obj_0000 and obj_0001?"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        assert response.status_code == 200
        data = response.json()

        assert "choices" in data
        metadata = data["choices"][0].get("basin_metadata", {})
        assert metadata.get("transport_mode") == "geometry_routed"

    def test_non_geometry_still_works(self, client):
        """Non-geometry prompts should still work (mock or LLM)."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Hello, how are you?"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        assert response.status_code == 200
        data = response.json()

        assert "choices" in data
        metadata = data["choices"][0].get("basin_metadata", {})
        # Should NOT be geometry_routed
        assert metadata.get("transport_mode") != "geometry_routed"

    def test_geometry_receipt_saved(self, client, tmp_path):
        """Verify loop receipt is saved to disk."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "What is the distance between obj_0000 and obj_0001?"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        assert response.status_code == 200
        data = response.json()

        # Check streaming_receipts
        if "streaming_receipts" in data and data["streaming_receipts"]:
            receipt = data["streaming_receipts"][0]
            assert "loop_id" in receipt
            assert receipt.get("work_order") == "WO-ECHO-FIBPI3D-LOOP-02"

    def test_geometry_confidence_threshold(self, client):
        """Verify low-confidence geometry prompts may not route."""
        # A prompt with just one keyword might have low confidence
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "I need to find something"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        assert response.status_code == 200
        data = response.json()

        # Single keyword "find" might trigger detection but with low confidence
        # The routing decision depends on confidence > 0.5 OR pattern match
        metadata = data["choices"][0].get("basin_metadata", {})

        # This is expected to either route or not based on implementation
        # Just verify it doesn't error
        assert "transport_mode" in metadata or True  # Always passes

    def test_geometry_with_multiple_patterns(self, client):
        """High-confidence geometry prompts should definitely route."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Find the 10 nearest objects to obj_0042 and calculate distance between obj_0000 and obj_0001"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        assert response.status_code == 200
        data = response.json()

        metadata = data["choices"][0].get("basin_metadata", {})
        assert metadata.get("transport_mode") == "geometry_routed"


# =============================================================================
# FastAPI TestClient Tests for Math Routing (WO-MODULAR-INVENTORY-01)
# =============================================================================

class TestChatMathRoutingTestClient:
    """
    Tests for math routing using FastAPI TestClient.

    Run with: BASIN_BACKEND=mock pytest tests/test_chat_geometry_routing.py::TestChatMathRoutingTestClient -v
    """

    @pytest.fixture
    def client(self):
        """Create TestClient for basin_server."""
        from fastapi.testclient import TestClient
        from api.basin_server import app
        return TestClient(app)

    def test_math_expression_routed(self, client):
        """Math expressions should be routed to calculator."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Calculate 15 + 27"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        assert response.status_code == 200
        data = response.json()

        assert "choices" in data
        metadata = data["choices"][0].get("basin_metadata", {})
        assert metadata.get("transport_mode") == "math_routed"

    def test_math_response_has_result(self, client):
        """Math responses should include calculated result."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "What is 10 * 5?"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()
        metadata = data["choices"][0].get("basin_metadata", {})

        if metadata.get("transport_mode") == "math_routed":
            assert "result" in metadata
            assert metadata["result"] == 50

    def test_math_has_router_schema_version(self, client):
        """Math responses should include router_schema_version."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Calculate 5 + 3"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()
        metadata = data["choices"][0].get("basin_metadata", {})

        if metadata.get("transport_mode") == "math_routed":
            assert "router_schema_version" in metadata
            assert metadata["router_schema_version"] == "1.0.0"


# =============================================================================
# FastAPI TestClient Tests for Retrieval Routing (WO-MODULAR-INVENTORY-01)
# =============================================================================

class TestChatRetrievalRoutingTestClient:
    """
    Tests for retrieval routing using FastAPI TestClient.

    Run with: BASIN_BACKEND=mock pytest tests/test_chat_geometry_routing.py::TestChatRetrievalRoutingTestClient -v
    """

    @pytest.fixture
    def client(self):
        """Create TestClient for basin_server."""
        from fastapi.testclient import TestClient
        from api.basin_server import app
        return TestClient(app)

    def test_search_query_routed(self, client):
        """Search queries should be routed to retrieval tool."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Search for documentation about Python decorators"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        assert response.status_code == 200
        data = response.json()

        assert "choices" in data
        metadata = data["choices"][0].get("basin_metadata", {})
        assert metadata.get("transport_mode") == "retrieval_routed"

    def test_retrieval_is_stub(self, client):
        """Retrieval responses should indicate stub status."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Look up information about machine learning"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()
        metadata = data["choices"][0].get("basin_metadata", {})

        if metadata.get("transport_mode") == "retrieval_routed":
            assert metadata.get("stub") is True


# =============================================================================
# FastAPI TestClient Tests for Code Routing (WO-MODULAR-INVENTORY-01)
# =============================================================================

class TestChatCodeRoutingTestClient:
    """
    Tests for code routing using FastAPI TestClient.

    Run with: BASIN_BACKEND=mock pytest tests/test_chat_geometry_routing.py::TestChatCodeRoutingTestClient -v
    """

    @pytest.fixture
    def client(self):
        """Create TestClient for basin_server."""
        from fastapi.testclient import TestClient
        from api.basin_server import app
        return TestClient(app)

    def test_code_execution_request_routed(self, client):
        """Code execution requests should be routed to code tool."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Execute this Python script: def hello(): pass"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        assert response.status_code == 200
        data = response.json()

        assert "choices" in data
        metadata = data["choices"][0].get("basin_metadata", {})
        assert metadata.get("transport_mode") == "code_routed"

    def test_code_is_stub(self, client):
        """Code responses should indicate stub status."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Execute this script: import os"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()
        metadata = data["choices"][0].get("basin_metadata", {})

        if metadata.get("transport_mode") == "code_routed":
            assert metadata.get("stub") is True


# =============================================================================
# Receipt Chain Verification Tests
# =============================================================================

class TestReceiptChainVerification:
    """
    Tests for receipt chain hash verification.

    Run with: BASIN_BACKEND=mock pytest tests/test_chat_geometry_routing.py::TestReceiptChainVerification -v
    """

    @pytest.fixture
    def client(self):
        """Create TestClient for basin_server."""
        from fastapi.testclient import TestClient
        from api.basin_server import app
        return TestClient(app)

    def test_chain_hash_links_all_steps(self, client):
        """Verify chain hash is computed and valid."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "What is the distance between obj_0000 and obj_0001?"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()

        if "streaming_receipts" in data and data["streaming_receipts"]:
            receipt = data["streaming_receipts"][0]

            # Verify chain hash exists and is valid SHA256 format
            assert "chain_hash" in receipt
            assert len(receipt["chain_hash"]) == 64
            assert all(c in "0123456789abcdef" for c in receipt["chain_hash"])

            # Verify all chain components are present
            assert "prompt_sha256" in receipt
            assert "router_decision" in receipt
            assert "tool_receipt_id" in receipt
            assert "answer_sha256" in receipt

            # Verify prompt_sha256 matches actual prompt
            import hashlib
            prompt = "What is the distance between obj_0000 and obj_0001?"
            expected_prompt_sha = hashlib.sha256(prompt.encode()).hexdigest()
            assert receipt["prompt_sha256"] == expected_prompt_sha

    def test_prompt_sha256_matches(self, client):
        """Verify prompt SHA256 matches actual prompt."""
        import hashlib

        prompt = "What is the distance between obj_0000 and obj_0001?"

        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": prompt}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()

        if "streaming_receipts" in data and data["streaming_receipts"]:
            receipt = data["streaming_receipts"][0]

            expected_sha = hashlib.sha256(prompt.encode()).hexdigest()
            assert receipt["prompt_sha256"] == expected_sha

    def test_chain_hash_re_derivable(self, client):
        """
        WO-TOOL-ROUTING-HARDEN-01: Verify chain hash can be re-derived from receipt.

        The chain hash should be computable from:
        - prompt_sha256
        - router_decision_hash_basis (raw dict)
        - tool_receipt_id
        - answer_sha256
        """
        import hashlib

        prompt = "What is the distance between obj_0000 and obj_0001?"

        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": prompt}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()

        if "streaming_receipts" in data and data["streaming_receipts"]:
            receipt = data["streaming_receipts"][0]

            # Check hash basis is present
            assert "router_decision_hash_basis" in receipt, \
                "Receipt should include router_decision_hash_basis for chain hash re-derivation"

            if receipt.get("router_decision_hash_basis"):
                # Re-derive chain hash
                hash_data = (
                    f"{receipt['prompt_sha256']}|"
                    f"{json.dumps(receipt['router_decision_hash_basis'], sort_keys=True)}|"
                    f"{receipt['tool_receipt_id']}|"
                    f"{receipt['answer_sha256']}"
                )
                expected_chain_hash = hashlib.sha256(hash_data.encode()).hexdigest()
                assert receipt["chain_hash"] == expected_chain_hash, \
                    "Chain hash should be re-derivable from receipt fields"


# =============================================================================
# Stub Status Tests (WO-TOOL-ROUTING-HARDEN-01)
# =============================================================================

class TestStubStatusHonesty:
    """
    Tests to verify stub routes use UNIMPLEMENTED status instead of PASS.

    WO-TOOL-ROUTING-HARDEN-01: Stubs should not emit status="PASS".
    """

    @pytest.fixture
    def client(self):
        """Create TestClient for basin_server."""
        from fastapi.testclient import TestClient
        from api.basin_server import app
        return TestClient(app)

    def test_retrieval_stub_status_not_pass(self, client):
        """Retrieval stub should use UNIMPLEMENTED status, not PASS."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Search for documentation about Python decorators"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()
        metadata = data["choices"][0].get("basin_metadata", {})

        if metadata.get("transport_mode") == "retrieval_routed":
            if "streaming_receipts" in data and data["streaming_receipts"]:
                receipt = data["streaming_receipts"][0]
                assert receipt["status"] == "UNIMPLEMENTED", \
                    "Stub route should use UNIMPLEMENTED status, not PASS"

    def test_code_stub_status_not_pass(self, client):
        """Code stub should use UNIMPLEMENTED status, not PASS."""
        response = client.post(
            "/v1/chat/completions",
            json={
                "messages": [{"role": "user", "content": "Execute this Python script: def hello(): pass"}],
                "temperature": 0.0,
                "top_p": 1.0,
                "max_tokens": 256,
                "quant": {
                    "phi0_deg": 0.0,
                    "cardinality": 2,
                    "control_consent": True,
                },
            },
        )

        data = response.json()
        metadata = data["choices"][0].get("basin_metadata", {})

        if metadata.get("transport_mode") == "code_routed":
            if "streaming_receipts" in data and data["streaming_receipts"]:
                receipt = data["streaming_receipts"][0]
                assert receipt["status"] == "UNIMPLEMENTED", \
                    "Stub route should use UNIMPLEMENTED status, not PASS"


# =============================================================================
# Handler Registry Tests (WO-TOOL-ROUTING-HARDEN-02)
# =============================================================================

class TestHandlerRegistry:
    """
    Tests for table-driven tool routing handler registry.

    WO-TOOL-ROUTING-HARDEN-02: Verifies registry completeness and correctness.
    """

    def test_handler_registry_exists(self):
        """Handler registry should exist in basin_server."""
        import api.basin_server as bs
        assert hasattr(bs, 'TOOL_HANDLER_REGISTRY'), \
            "basin_server should have TOOL_HANDLER_REGISTRY"

    def test_all_tool_policies_registered(self):
        """Every tool-routed policy should have a handler registered."""
        import api.basin_server as bs

        for policy in get_tool_routed_policies():
            assert policy.mode in bs.TOOL_HANDLER_REGISTRY, \
                f"Missing handler for {policy.mode.value}"

    def test_registry_entries_are_callable_pairs(self):
        """Registry entries should be (handler, detector) callable tuples."""
        import api.basin_server as bs

        for mode, entry in bs.TOOL_HANDLER_REGISTRY.items():
            assert isinstance(entry, tuple), f"Entry for {mode} should be tuple"
            assert len(entry) == 2, f"Entry for {mode} should have 2 elements"
            handler, detector = entry
            assert callable(handler), f"Handler for {mode} should be callable"
            assert callable(detector), f"Detector for {mode} should be callable"

    def test_geometry_handler_registered(self):
        """Geometry handler should be registered."""
        import api.basin_server as bs

        assert TransportMode.GEOMETRY_ROUTED in bs.TOOL_HANDLER_REGISTRY
        handler, detector = bs.TOOL_HANDLER_REGISTRY[TransportMode.GEOMETRY_ROUTED]
        assert handler == bs._handle_geometry_chat
        assert detector == detect_geometry_intent

    def test_math_handler_registered(self):
        """Math handler should be registered."""
        import api.basin_server as bs

        assert TransportMode.MATH_ROUTED in bs.TOOL_HANDLER_REGISTRY
        handler, detector = bs.TOOL_HANDLER_REGISTRY[TransportMode.MATH_ROUTED]
        assert handler == bs._handle_math_chat
        assert detector == detect_math_intent

    def test_retrieval_handler_registered(self):
        """Retrieval handler should be registered."""
        import api.basin_server as bs

        assert TransportMode.RETRIEVAL_ROUTED in bs.TOOL_HANDLER_REGISTRY
        handler, detector = bs.TOOL_HANDLER_REGISTRY[TransportMode.RETRIEVAL_ROUTED]
        assert handler == bs._handle_retrieval_chat
        assert detector == detect_retrieval_intent

    def test_code_handler_registered(self):
        """Code handler should be registered."""
        import api.basin_server as bs

        assert TransportMode.CODE_ROUTED in bs.TOOL_HANDLER_REGISTRY
        handler, detector = bs.TOOL_HANDLER_REGISTRY[TransportMode.CODE_ROUTED]
        assert handler == bs._handle_code_chat
        assert detector == detect_code_intent

    def test_route_to_tool_function_exists(self):
        """_route_to_tool helper function should exist."""
        import api.basin_server as bs
        assert hasattr(bs, '_route_to_tool'), \
            "basin_server should have _route_to_tool function"
        assert callable(bs._route_to_tool), \
            "_route_to_tool should be callable"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
