"""
Patcher for LangChain (langchain-core).
Wraps: BaseLLM._generate, BaseChatModel._generate, BaseTool._run
"""

import time
import functools
import logging
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from ..emitter import NotiLensEmitter
    from ..loop_detector import LoopDetector

logger = logging.getLogger("notilens.langchain")

_patched = False


def patch(emitter: "NotiLensEmitter", loop_detector: "LoopDetector", timeout: float) -> bool:
    global _patched
    if _patched:
        return True
    try:
        from langchain_core.language_models.llms import BaseLLM
        from langchain_core.language_models.chat_models import BaseChatModel
        from langchain_core.tools import BaseTool

        # ── LLM generate ──────────────────────────────────────────────────
        _orig_llm = BaseLLM._generate

        @functools.wraps(_orig_llm)
        def _llm_generate(self, prompts, *args, **kwargs):
            start = time.time()
            name = getattr(self, "model_name", type(self).__name__)

            loop_msg = loop_detector.record()
            if loop_msg:
                emitter.send("task.loop", loop_msg, level="warning", meta={"model": name, "source": "langchain"})

            emitter.send("ai.call.start", f"LangChain LLM — {name}",
                         level="info", meta={"model": name, "source": "langchain"})
            try:
                result = _orig_llm(self, prompts, *args, **kwargs)
                duration = round(time.time() - start, 3)
                if duration > timeout:
                    emitter.send("task.timeout", f"LangChain LLM took {duration}s",
                                 level="warning", meta={"duration": duration, "source": "langchain"})
                emitter.send("ai.call.complete", f"LangChain LLM done in {duration}s",
                             level="info", meta={"model": name, "duration": duration, "source": "langchain"})
                return result
            except Exception as exc:
                emitter.send("task.error", f"LangChain LLM error: {exc}",
                             level="error", meta={"model": name, "error": str(exc), "source": "langchain"})
                raise

        BaseLLM._generate = _llm_generate

        # ── Chat model generate ────────────────────────────────────────────
        _orig_chat = BaseChatModel._generate

        @functools.wraps(_orig_chat)
        def _chat_generate(self, messages, *args, **kwargs):
            start = time.time()
            name = getattr(self, "model_name", type(self).__name__)

            loop_msg = loop_detector.record()
            if loop_msg:
                emitter.send("task.loop", loop_msg, level="warning", meta={"model": name, "source": "langchain"})

            emitter.send("ai.call.start", f"LangChain chat — {name}",
                         level="info", meta={"model": name, "messages": len(messages), "source": "langchain"})
            try:
                result = _orig_chat(self, messages, *args, **kwargs)
                duration = round(time.time() - start, 3)
                if duration > timeout:
                    emitter.send("task.timeout", f"LangChain chat took {duration}s",
                                 level="warning", meta={"duration": duration, "source": "langchain"})
                emitter.send("ai.call.complete", f"LangChain chat done in {duration}s",
                             level="info", meta={"model": name, "duration": duration, "source": "langchain"})
                return result
            except Exception as exc:
                emitter.send("task.error", f"LangChain chat error: {exc}",
                             level="error", meta={"model": name, "error": str(exc), "source": "langchain"})
                raise

        BaseChatModel._generate = _chat_generate

        # ── Tool run ──────────────────────────────────────────────────────
        _orig_tool = BaseTool._run

        @functools.wraps(_orig_tool)
        def _tool_run(self, *args, **kwargs):
            tool_name = getattr(self, "name", type(self).__name__)
            start = time.time()
            emitter.send("ai.call.start", f"Tool invoked: {tool_name}",
                         level="info", meta={"tool": tool_name, "source": "langchain"})
            try:
                result = _orig_tool(self, *args, **kwargs)
                duration = round(time.time() - start, 3)
                emitter.send("ai.call.complete", f"Tool {tool_name} done in {duration}s",
                             level="info", meta={"tool": tool_name, "duration": duration, "source": "langchain"})
                return result
            except Exception as exc:
                emitter.send("task.error", f"Tool {tool_name} error: {exc}",
                             level="error", meta={"tool": tool_name, "error": str(exc), "source": "langchain"})
                raise

        BaseTool._run = _tool_run

        _patched = True
        logger.debug("NotiLens: LangChain patched")
        return True

    except ImportError:
        return False
    except Exception as e:
        logger.warning("NotiLens: failed to patch LangChain — %s", e)
        return False
