"""Obol interceptor for LangChain.

Patches BaseTool.invoke() to capture all tool executions.

Usage:
    from obol.interceptors import langchain
    langchain.install()

    # Then use LangChain normally - all tool calls are logged
"""
import functools
from obol.interceptors._base import send_event, _content_hash

_installed = False
_original_invoke = None
_original_ainvoke = None


def _extract_target(tool, tool_input):
    """Extract a meaningful target from tool input."""
    if isinstance(tool_input, dict):
        # Common patterns
        for key in ("file_path", "path", "url", "query", "command", "input"):
            if key in tool_input:
                return tool_input[key]
        # Fall back to hash
        return _content_hash(tool_input)
    return str(tool_input)[:500] if tool_input else None


def _traced_invoke(original):
    """Create a traced version of BaseTool.invoke."""
    @functools.wraps(original)
    def wrapper(self, tool_input, config=None, **kwargs):
        tool_name = getattr(self, "name", self.__class__.__name__)
        target = _extract_target(self, tool_input)

        # Log tool start
        send_event(
            platform="langchain",
            action=f"Tool.{tool_name}",
            target=target,
            result="pending",
            metadata={
                "tool_class": self.__class__.__name__,
                "input_type": type(tool_input).__name__,
            }
        )

        try:
            result = original(self, tool_input, config=config, **kwargs)

            # Log tool success
            send_event(
                platform="langchain",
                action=f"Tool.{tool_name}",
                target=target,
                result="success",
                metadata={
                    "tool_class": self.__class__.__name__,
                    "output_type": type(result).__name__,
                    "output_size": len(str(result)) if result else 0,
                }
            )

            return result

        except Exception as e:
            # Log tool error
            send_event(
                platform="langchain",
                action=f"Tool.{tool_name}",
                target=target,
                result="error",
                metadata={
                    "tool_class": self.__class__.__name__,
                    "error_type": type(e).__name__,
                    "error_message": str(e)[:500],
                }
            )
            raise

    return wrapper


def _traced_ainvoke(original):
    """Create a traced version of BaseTool.ainvoke (async)."""
    @functools.wraps(original)
    async def wrapper(self, tool_input, config=None, **kwargs):
        tool_name = getattr(self, "name", self.__class__.__name__)
        target = _extract_target(self, tool_input)

        # Log tool start
        send_event(
            platform="langchain",
            action=f"Tool.{tool_name}",
            target=target,
            result="pending",
            metadata={
                "tool_class": self.__class__.__name__,
                "input_type": type(tool_input).__name__,
                "async": True,
            }
        )

        try:
            result = await original(self, tool_input, config=config, **kwargs)

            # Log tool success
            send_event(
                platform="langchain",
                action=f"Tool.{tool_name}",
                target=target,
                result="success",
                metadata={
                    "tool_class": self.__class__.__name__,
                    "output_type": type(result).__name__,
                    "output_size": len(str(result)) if result else 0,
                    "async": True,
                }
            )

            return result

        except Exception as e:
            # Log tool error
            send_event(
                platform="langchain",
                action=f"Tool.{tool_name}",
                target=target,
                result="error",
                metadata={
                    "tool_class": self.__class__.__name__,
                    "error_type": type(e).__name__,
                    "error_message": str(e)[:500],
                    "async": True,
                }
            )
            raise

    return wrapper


def install():
    """Install the LangChain interceptor.

    Patches BaseTool.invoke() and BaseTool.ainvoke() to capture all tool calls.
    Safe to call multiple times.
    """
    global _installed, _original_invoke, _original_ainvoke

    if _installed:
        return

    try:
        from langchain_core.tools import BaseTool
    except ImportError:
        try:
            from langchain.tools import BaseTool
        except ImportError:
            return  # LangChain not installed

    # Store originals
    _original_invoke = BaseTool.invoke
    _original_ainvoke = BaseTool.ainvoke

    # Patch
    BaseTool.invoke = _traced_invoke(_original_invoke)
    BaseTool.ainvoke = _traced_ainvoke(_original_ainvoke)

    _installed = True


def uninstall():
    """Remove the LangChain interceptor."""
    global _installed, _original_invoke, _original_ainvoke

    if not _installed:
        return

    try:
        from langchain_core.tools import BaseTool
    except ImportError:
        try:
            from langchain.tools import BaseTool
        except ImportError:
            return

    if _original_invoke:
        BaseTool.invoke = _original_invoke
    if _original_ainvoke:
        BaseTool.ainvoke = _original_ainvoke

    _installed = False


def is_installed():
    """Check if the interceptor is currently installed."""
    return _installed
