#!/usr/bin/env python3
"""
WO-ECHO-FIBPI3D-GRAPH-01: Spatial Tool Graph Integration Test

Tests that:
1. Substrate graph contains fibpi3d_spatial_tool node with requirements
2. Echo can call the spatial tool endpoint
3. Receipt chain is saved correctly
4. Determinism hash is present in response

This is the "Echo chooses spatial tool for geometry prompt" integration test.
"""
import json
import hashlib
import pytest
import requests
from pathlib import Path
from datetime import datetime, timezone

# Config
ENDPOINT = "http://127.0.0.1:8001"
GRAPH_PATH = Path(__file__).parent.parent / "ECHO_DUAL_BRAIN_GRAPH.json"
RECEIPTS_DIR = Path(__file__).parent.parent / "receipts" / "spatial_tool_integration"


def load_graph():
    """Load the substrate graph."""
    return json.loads(GRAPH_PATH.read_text())


class TestSpatialToolGraphNode:
    """Test that the graph node is correctly defined."""

    def test_graph_has_spatial_tool_node(self):
        """Verify fibpi3d_spatial_tool node exists in graph."""
        graph = load_graph()
        nodes = {n["id"]: n for n in graph["nodes"]}

        assert "fibpi3d_spatial_tool" in nodes, "fibpi3d_spatial_tool node missing"
        node = nodes["fibpi3d_spatial_tool"]

        assert node["type"] == "tool_endpoint"
        assert node["port"] == 8001
        assert "POST /echo/spatial" in node["route"]

    def test_node_has_receipt_requirements(self):
        """Verify node specifies receipt emission requirements."""
        graph = load_graph()
        nodes = {n["id"]: n for n in graph["nodes"]}
        node = nodes["fibpi3d_spatial_tool"]

        reqs = node.get("requirements", {})
        assert reqs.get("receipt_emission") is True, "receipt_emission requirement missing"
        assert reqs.get("determinism_hash") == "scene_hash", "determinism_hash field missing"
        assert reqs.get("receipt_id_field") == "receipt_id", "receipt_id_field missing"

    def test_node_has_schema_pointer(self):
        """Verify node has schema pointers."""
        graph = load_graph()
        nodes = {n["id"]: n for n in graph["nodes"]}
        node = nodes["fibpi3d_spatial_tool"]

        schema = node.get("schema", {})
        assert "SpatialMemoryRequest" in schema.get("request", "")
        assert "SpatialMemoryResponse" in schema.get("response", "")

    def test_graph_has_edge_to_spatial_tool(self):
        """Verify there's an edge from orchestrator to spatial tool."""
        graph = load_graph()
        edges = graph["edges"]

        spatial_edges = [e for e in edges if e.get("to") == "fibpi3d_spatial_tool"]
        assert len(spatial_edges) >= 1, "No edges to fibpi3d_spatial_tool"

        # Check at least one edge has gate requirements
        has_gate_reqs = any(
            e.get("gate_requirements", {}).get("must_produce_receipt")
            for e in spatial_edges
        )
        assert has_gate_reqs, "No edge with gate_requirements.must_produce_receipt"


@pytest.mark.skipif(
    not requests.get(f"{ENDPOINT}/health", timeout=2).ok if True else True,
    reason="Echo server not running on port 8001"
)
class TestSpatialToolEndpointIntegration:
    """Test the actual endpoint integration."""

    @pytest.fixture(autouse=True)
    def check_server(self):
        """Skip if server not running."""
        try:
            resp = requests.get(f"{ENDPOINT}/health", timeout=2)
            if not resp.ok:
                pytest.skip("Echo server not healthy")
        except requests.exceptions.RequestException:
            pytest.skip("Echo server not running")

    def test_spatial_query_returns_receipt(self):
        """Verify spatial query returns receipt_id."""
        query = {
            "scene_id": "integration_test",
            "queries": [
                {"query_type": "k_nearest", "a": "obj_0000", "k": 3}
            ],
            "create_scene_if_missing": True,
            "scene_config": {"n_objects": 50, "seed": "42"}
        }

        resp = requests.post(f"{ENDPOINT}/echo/spatial", json=query, timeout=10)
        assert resp.ok, f"Spatial query failed: {resp.text}"

        data = resp.json()
        assert "receipt_id" in data, "receipt_id missing from response"
        assert data["receipt_id"], "receipt_id is empty"

    def test_spatial_query_returns_determinism_hash(self):
        """Verify spatial query returns scene_hash for determinism."""
        query = {
            "scene_id": "determinism_test",
            "queries": [
                {"query_type": "distance", "a": "obj_0000", "b": "obj_0001"}
            ],
            "create_scene_if_missing": True,
            "scene_config": {"n_objects": 50, "seed": "determinism_42"}
        }

        resp = requests.post(f"{ENDPOINT}/echo/spatial", json=query, timeout=10)
        assert resp.ok

        data = resp.json()
        assert "scene_hash" in data, "scene_hash missing from response"
        assert len(data["scene_hash"]) == 64, "scene_hash should be SHA256 (64 hex chars)"

    def test_determinism_hash_is_reproducible(self):
        """Verify same scene produces same hash."""
        query = {
            "scene_id": "reproducibility_test",
            "queries": [
                {"query_type": "k_nearest", "a": "obj_0000", "k": 5}
            ],
            "create_scene_if_missing": True,
            "scene_config": {"n_objects": 100, "seed": "repro_seed_42"}
        }

        resp1 = requests.post(f"{ENDPOINT}/echo/spatial", json=query, timeout=10)
        resp2 = requests.post(f"{ENDPOINT}/echo/spatial", json=query, timeout=10)

        assert resp1.ok and resp2.ok

        data1 = resp1.json()
        data2 = resp2.json()

        assert data1["scene_hash"] == data2["scene_hash"], "scene_hash not deterministic"

        # Compare results excluding latency_us (which varies)
        def strip_latency(results):
            return [{k: v for k, v in r.items() if k != "latency_us"} for r in results]

        assert strip_latency(data1["results"]) == strip_latency(data2["results"]), "results not deterministic"

    def test_save_receipt_chain(self):
        """Integration test: query + save receipt chain."""
        RECEIPTS_DIR.mkdir(parents=True, exist_ok=True)

        query = {
            "scene_id": "receipt_chain_test",
            "queries": [
                {"query_type": "k_nearest", "a": "obj_0000", "k": 5},
                {"query_type": "distance", "a": "obj_0000", "b": "obj_0001"},
                {"query_type": "near", "a": "obj_0000", "b": "obj_0001", "threshold": 5.0}
            ],
            "create_scene_if_missing": True,
            "scene_config": {"n_objects": 100, "seed": "receipt_chain_42"}
        }

        resp = requests.post(f"{ENDPOINT}/echo/spatial", json=query, timeout=10)
        assert resp.ok

        data = resp.json()

        # Build receipt chain
        receipt_chain = {
            "wo": "WO-ECHO-FIBPI3D-GRAPH-01",
            "timestamp_utc": datetime.now(timezone.utc).isoformat(),
            "test": "spatial_tool_graph_integration",
            "endpoint": f"{ENDPOINT}/echo/spatial",
            "request": query,
            "response": {
                "scene_id": data["scene_id"],
                "scene_hash": data["scene_hash"],
                "n_objects": data["n_objects"],
                "receipt_id": data["receipt_id"],
                "total_latency_ms": data["total_latency_ms"],
                "n_queries": len(data["results"]),
                "all_queries_succeeded": all(r.get("error") is None for r in data["results"])
            },
            "graph_node": "fibpi3d_spatial_tool",
            "gate_requirements_met": {
                "receipt_emission": bool(data.get("receipt_id")),
                "determinism_hash": bool(data.get("scene_hash"))
            }
        }

        # Compute receipt hash
        receipt_bytes = json.dumps(receipt_chain, sort_keys=True).encode()
        receipt_chain["receipt_sha256"] = hashlib.sha256(receipt_bytes).hexdigest()

        # Save
        receipt_path = RECEIPTS_DIR / f"spatial_integration_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        receipt_path.write_text(json.dumps(receipt_chain, indent=2))

        # Verify
        assert receipt_chain["gate_requirements_met"]["receipt_emission"]
        assert receipt_chain["gate_requirements_met"]["determinism_hash"]
        assert receipt_chain["response"]["all_queries_succeeded"]

        print(f"\nReceipt saved: {receipt_path}")
        print(f"Receipt SHA256: {receipt_chain['receipt_sha256'][:16]}...")


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