import os
import asyncio
from typing import Optional, List, Dict, Any, Union

from clawagents.providers.llm import LLMProvider
from clawagents.tools.registry import ToolRegistry, Tool, ToolResult
from clawagents.graph.agent_loop import run_agent_graph, AgentState


class LangChainToolAdapter:
    """
    Wraps a LangChain-style tool (with .ainvoke / .invoke) into a
    ClawAgent-compatible Tool with .execute().
    """
    def __init__(self, lc_tool):
        self.name = getattr(lc_tool, "name", type(lc_tool).__name__)
        self.description = getattr(lc_tool, "description", "")
        self.parameters = self._extract_params(lc_tool)
        self._lc_tool = lc_tool

    def _extract_params(self, lc_tool) -> Dict[str, Dict[str, Any]]:
        schema = getattr(lc_tool, "args_schema", None)
        if schema and hasattr(schema, "schema"):
            try:
                s = schema.schema()
                props = s.get("properties", {})
                required = s.get("required", [])
                return {
                    k: {
                        "type": v.get("type", "string"),
                        "description": v.get("description", ""),
                        "required": k in required,
                    }
                    for k, v in props.items()
                }
            except Exception:
                pass
        return {}

    async def execute(self, args: Dict[str, Any]) -> ToolResult:
        try:
            if hasattr(self._lc_tool, "ainvoke"):
                result = await self._lc_tool.ainvoke(args)
            elif hasattr(self._lc_tool, "invoke"):
                result = self._lc_tool.invoke(args)
            else:
                result = self._lc_tool.run(**args)
            return ToolResult(success=True, output=str(result))
        except Exception as e:
            return ToolResult(success=False, output="", error=str(e))


class ClawAgent:
    def __init__(
        self,
        llm: LLMProvider,
        tools: ToolRegistry,
        system_prompt: Optional[str] = None,
        streaming: bool = True,
    ):
        self.llm = llm
        self.tools = tools
        self.system_prompt = system_prompt
        self.streaming = streaming

    async def invoke(self, task: str, max_iterations: int = 3) -> AgentState:
        """
        Executes the agent graph for a given task.
        """
        return await run_agent_graph(
            task=task,
            llm=self.llm,
            tools=self.tools,
            system_prompt=self.system_prompt,
            max_iterations=max_iterations,
            streaming=self.streaming,
        )


def create_claw_agent(
    model: LLMProvider,
    tools: Optional[List] = None,
    system_prompt: Optional[str] = None,
    skills: Optional[List[Union[str, os.PathLike]]] = None,
    streaming: bool = True,
) -> ClawAgent:
    """
    Factory function to initialize a ClawAgent.

    Args:
        model:         An LLMProvider (OpenAI / Gemini).
        tools:         A list of Tool objects. LangChain tools (with .ainvoke)
                       are automatically adapted.
        system_prompt: Optional system prompt for the agent.
        skills:        Optional list of directory paths containing SKILL.md files.
                       Skill tools (list_skills, use_skill) are auto-registered.

    Example:
        agent = create_claw_agent(
            model=provider,
            tools=[tavily_search, think_tool],
            system_prompt="You are a research assistant.",
            skills=["./skills", "../shared/skills"],
        )
        result = await agent.invoke("Summarize the latest news")
    """
    registry = ToolRegistry()

    # ── Adapt and register tools ───────────────────────────────────────
    if tools:
        for tool in tools:
            if hasattr(tool, "ainvoke") and not hasattr(tool, "execute"):
                registry.register(LangChainToolAdapter(tool))
            else:
                registry.register(tool)

    # ── Load skills from directories ───────────────────────────────────
    if skills:
        from clawagents.tools.skills import SkillStore, create_skill_tools

        skill_store = SkillStore()
        for d in skills:
            if os.path.exists(str(d)):
                skill_store.add_directory(d)

        # Async-safe loading: run inside existing loop or create one
        try:
            loop = asyncio.get_running_loop()
            # We're inside an async context — schedule and await
            import concurrent.futures
            with concurrent.futures.ThreadPoolExecutor() as pool:
                loop.run_in_executor(pool, lambda: asyncio.run(skill_store.load_all()))
        except RuntimeError:
            # No running loop — safe to call asyncio.run directly
            asyncio.run(skill_store.load_all())

        for skill_tool in create_skill_tools(skill_store):
            registry.register(skill_tool)

    return ClawAgent(
        llm=model, tools=registry, system_prompt=system_prompt, streaming=streaming
    )
