"""Tests for the LangChain integration.

Since langchain-core is an optional dependency, we mock the import
and test the core logic.
"""

from __future__ import annotations

import sys
from types import ModuleType
from unittest.mock import MagicMock
from uuid import uuid4

import httpx
import pytest
import respx

from vorim import Vorim

from .conftest import (
    AGENT_ID,
    API_KEY,
    AUDIT_RESPONSE,
    BASE_URL,
    PERMISSION_CHECK_ALLOWED,
    PERMISSION_CHECK_DENIED,
    REGISTRATION_DATA,
)

# ── Mock langchain_core so imports succeed ───────────────────────────────

_mock_lc_core = ModuleType("langchain_core")
_mock_lc_callbacks = ModuleType("langchain_core.callbacks")
_mock_lc_tools = ModuleType("langchain_core.tools")


class _MockBaseCallbackHandler:
    def __init__(self):
        pass


class _MockBaseTool:
    name: str = "mock_tool"

    def _run(self, *args, **kwargs):
        return "mock result"


class _MockStructuredTool(_MockBaseTool):
    @classmethod
    def from_function(cls, func, name, description):
        tool = cls()
        tool.name = name
        tool._run = func
        return tool


_mock_lc_callbacks.BaseCallbackHandler = _MockBaseCallbackHandler  # type: ignore
_mock_lc_tools.BaseTool = _MockBaseTool  # type: ignore
_mock_lc_tools.StructuredTool = _MockStructuredTool  # type: ignore
_mock_lc_core.callbacks = _mock_lc_callbacks  # type: ignore
_mock_lc_core.tools = _mock_lc_tools  # type: ignore

sys.modules.setdefault("langchain_core", _mock_lc_core)
sys.modules.setdefault("langchain_core.callbacks", _mock_lc_callbacks)
sys.modules.setdefault("langchain_core.tools", _mock_lc_tools)

from vorim.integrations.langchain import (
    VorimCallbackHandler,
    create_vorim_agent,
    vorim_tool,
    wrap_tool,
    wrap_tools,
)


class TestVorimToolDecorator:
    @respx.mock
    def test_success(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_ALLOWED)
        )
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)

        @vorim_tool(vorim, agent_id=AGENT_ID, permission="agent:execute")
        def search(query: str) -> str:
            """Search docs."""
            return f"Results for {query}"

        result = search._run("AI papers")
        assert result == "Results for AI papers"
        vorim.close()

    @respx.mock
    def test_denied(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_DENIED)
        )
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)

        @vorim_tool(vorim, agent_id=AGENT_ID)
        def search(query: str) -> str:
            """Search."""
            return query

        with pytest.raises(PermissionError, match="permission denied"):
            search._run("secret")
        vorim.close()


class TestWrapTool:
    @respx.mock
    def test_wrap_success(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_ALLOWED)
        )
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        tool = _MockBaseTool()
        wrapped = wrap_tool(tool, vorim, AGENT_ID, "agent:read")
        result = wrapped._run()
        assert result == "mock result"
        vorim.close()

    @respx.mock
    def test_wrap_denied(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_DENIED)
        )
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        tool = _MockBaseTool()
        wrapped = wrap_tool(tool, vorim, AGENT_ID)
        with pytest.raises(PermissionError, match="permission denied"):
            wrapped._run()
        vorim.close()


class TestWrapTools:
    @respx.mock
    def test_wrap_multiple(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_ALLOWED)
        )
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        tools = [_MockBaseTool(), _MockBaseTool()]
        wrapped = wrap_tools(tools, vorim, AGENT_ID)
        assert len(wrapped) == 2
        vorim.close()


class TestVorimCallbackHandler:
    @respx.mock
    def test_tool_start_end(self):
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        handler = VorimCallbackHandler(vorim, AGENT_ID)

        run_id = uuid4()
        handler.on_tool_start(
            {"name": "search"},
            "test query",
            run_id=run_id,
        )
        handler.on_tool_end("result", run_id=run_id)
        assert len(respx.calls) == 1  # one audit emit
        vorim.close()

    @respx.mock
    def test_tool_error(self):
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        handler = VorimCallbackHandler(vorim, AGENT_ID)

        run_id = uuid4()
        handler.on_tool_start({"name": "bad"}, "input", run_id=run_id)
        handler.on_tool_error(RuntimeError("fail"), run_id=run_id)
        assert len(respx.calls) == 1
        vorim.close()

    def test_end_without_start(self):
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        handler = VorimCallbackHandler(vorim, AGENT_ID)
        handler.on_tool_end("result", run_id=uuid4())  # should not raise
        vorim.close()


class TestCreateVorimAgent:
    @respx.mock
    def test_create(self):
        respx.post(f"{BASE_URL}/v1/agents").mock(
            return_value=httpx.Response(200, json=REGISTRATION_DATA)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        result = create_vorim_agent(
            vorim,
            name="test-agent",
            capabilities=["search"],
            scopes=["agent:read", "agent:execute"],
            tools=[_MockBaseTool()],
        )
        assert result["agent_id"] == AGENT_ID
        assert result["private_key"] == "ed25519-private-key"
        assert len(result["tools"]) == 1
        vorim.close()
