"""Integration tests against the REAL langchain_core dispatch machinery.

These run only when ``langchain_core`` is importable (it is not a project
dependency); otherwise the whole module is skipped. They prove the claims the
fake-executor tests can only simulate: our handler is a real
``BaseCallbackHandler``, and LangChain's own callback dispatch aborts an
undeclared tool before its body executes.
"""
from __future__ import annotations

import pytest

langchain_core = pytest.importorskip("langchain_core")

from langchain_core.callbacks import BaseCallbackHandler  # noqa: E402
from langchain_core.tools import tool  # noqa: E402

from abom import sign  # noqa: E402
from abom.adapters.langchain import AbomLangChainHandler  # noqa: E402
from abom.audit import verify_chain  # noqa: E402
from abom.bom import build_composition  # noqa: E402
from abom.gate import ActionDenied  # noqa: E402


def _manifest():
    key = sign.load_or_create_key()
    return build_composition(
        agent={"name": "real-lc-agent", "version": "1.0.0"},
        components=[
            {"type": "model", "name": "claude-3-5-sonnet-20241022"},
            {"type": "tool", "name": "search_kyc"},
        ],
        controls={},
        key=key,
    )


def test_handler_is_a_real_base_callback_handler():
    h = AbomLangChainHandler(manifest=_manifest())
    assert isinstance(h, BaseCallbackHandler)


def test_real_dispatch_records_declared_tool():
    h = AbomLangChainHandler(manifest=_manifest(), gate=True)

    @tool
    def search_kyc(query: str) -> str:
        """Search KYC documents."""
        return f"results for {query}"

    out = search_kyc.invoke("cust-4471", config={"callbacks": [h]})
    assert out == "results for cust-4471"
    assert len(h.abom.chain) == 1
    assert h.abom.chain[0]["data"]["tools_invoked"][0]["name"] == "search_kyc"
    assert verify_chain(h.abom.chain)["valid"]


def test_real_dispatch_denies_undeclared_tool_before_execution(tmp_path):
    sentinel = tmp_path / "money-moved.txt"
    h = AbomLangChainHandler(manifest=_manifest(), gate=True)

    @tool
    def wire_transfer(amount: str) -> str:
        """Wire money."""
        sentinel.write_text("MONEY MOVED")  # must never run
        return "done"

    with pytest.raises(ActionDenied):
        wire_transfer.invoke("1000000", config={"callbacks": [h]})

    assert not sentinel.exists()  # LangChain's own dispatch aborted pre-execution
    assert h.abom.chain == []
