"""Agent detection utilities for CLI.

Handles detection of Python agent handlers and wrapper generation.
Uses the new discovery system for robust entry point detection.
"""

from __future__ import annotations

from pathlib import Path

from khaos.discovery import (
    AgentEntryPoint,
    EntryPointType,
    detect_entry_points,
    select_best_entry_point,
)
from khaos.state import get_state_dir


def detect_python_agent_handler(script_path: Path) -> tuple[bool, str | None, list[str]]:
    """Return (use_runner, handler_name, candidates).

    Uses the discovery system to find @khaosagent entry points.
    All agents are wrapped through the standard Khaos runner.
    """
    try:
        source = script_path.read_text(encoding="utf-8")
    except OSError:
        return (False, None, [])

    # Use discovery system to find entry points
    entry_points = detect_entry_points(source)

    if not entry_points:
        return (True, None, [])

    # Get all candidate names
    candidates = [ep.name for ep in entry_points]

    # Select best entry point
    best = select_best_entry_point(entry_points)
    if best:
        return (True, best.name, candidates)

    return (True, None, candidates)


def get_entry_point_info(script_path: Path) -> AgentEntryPoint | None:
    """Get detailed info about the best entry point.

    Returns the AgentEntryPoint with full metadata for advanced usage.
    """
    try:
        source = script_path.read_text(encoding="utf-8")
    except OSError:
        return None

    entry_points = detect_entry_points(source)
    return select_best_entry_point(entry_points)


def stable_wrapper_path(script_path: Path) -> Path:
    """Get stable wrapper path for a script.

    Store wrappers in state dir (not the user's repo) to avoid polluting projects.
    This also makes `khaos test` safe to run in read-only repos.
    """
    import hashlib

    try:
        digest = hashlib.sha256(str(script_path).encode("utf-8")).hexdigest()[:10]
    except Exception:  # pragma: no cover - defensive
        digest = "unknown"
    wrappers_dir = get_state_dir() / "wrappers"
    wrappers_dir.mkdir(parents=True, exist_ok=True)
    return wrappers_dir / f"agent-wrapper-{script_path.stem}-{digest}.py"


def render_agent_wrapper(
    *,
    user_script: Path,
    delegate: str | None,
    candidates: list[str],
    entry_point: AgentEntryPoint | None = None,
) -> str:
    """Render a wrapper script for a Python agent.

    Now supports class-based agents with invoke methods.
    """
    options = ", ".join(candidates) if candidates else "(none detected)"
    chosen = delegate or "CHANGE_ME"

    # Determine if this is a class-based agent
    is_class = entry_point and entry_point.entry_type in (
        EntryPointType.CLASS,
        EntryPointType.DECORATED_CLASS,
        EntryPointType.CLASS_INSTANCE,
    )

    invoke_method = entry_point.invoke_method if entry_point else None

    if is_class:
        return _render_class_wrapper(
            user_script=user_script,
            class_name=chosen,
            invoke_method=invoke_method or "run",
            candidates=candidates,
            is_async=entry_point.is_async if entry_point else False,
        )

    return _render_function_wrapper(
        user_script=user_script,
        delegate=chosen,
        candidates=candidates,
        is_async=entry_point.is_async if entry_point else False,
    )


def _render_function_wrapper(
    *,
    user_script: Path,
    delegate: str,
    candidates: list[str],
    is_async: bool,
) -> str:
    """Render wrapper for function-based agent."""
    options = ", ".join(candidates) if candidates else "(none detected)"
    return (
        "from __future__ import annotations\n\n"
        "# Auto-generated by Khaos.\n"
        "# Purpose: provide a deterministic Python handler boundary for agent frameworks/scripts.\n\n"
        "import asyncio\n"
        "import importlib.util\n"
        "import inspect\n"
        "from pathlib import Path\n"
        "from typing import Any\n\n"
        "from khaos import khaos_agent\n"
        "from khaos.discovery.invoker import adapt_signature, extract_prompt\n\n"
        f"USER_SCRIPT = Path({str(user_script)!r}).expanduser().resolve()\n"
        f"DELEGATE = {delegate!r}  # candidates: {options}\n\n"
        "def _load() -> Any:\n"
        "    spec = importlib.util.spec_from_file_location('khaos_user_agent', USER_SCRIPT)\n"
        "    if spec is None or spec.loader is None:\n"
        "        raise RuntimeError(f'Failed to import user script: {USER_SCRIPT}')\n"
        "    module = importlib.util.module_from_spec(spec)\n"
        "    spec.loader.exec_module(module)\n"
        "    return module\n\n"
        "@khaos_agent\n"
        "def handle(message: dict) -> dict:\n"
        "    module = _load()\n"
        "    fn = getattr(module, DELEGATE, None)\n"
        "    if not callable(fn):\n"
        "        raise RuntimeError(\n"
        "            f\"Delegate '{DELEGATE}' not found/callable in {USER_SCRIPT}. \"\n"
        "            \"Edit DELEGATE to match your entrypoint function.\"\n"
        "        )\n"
        "    args, kwargs = adapt_signature(fn, message)\n"
        "    if kwargs:\n"
        "        result = fn(**kwargs)\n"
        "    elif args:\n"
        "        result = fn(*args)\n"
        "    else:\n"
        "        result = fn()\n"
        "    if inspect.isawaitable(result):\n"
        "        result = asyncio.get_event_loop().run_until_complete(result)\n"
        "    if isinstance(result, dict):\n"
        "        return result\n"
        "    return {'value': result}\n"
    )


def _render_class_wrapper(
    *,
    user_script: Path,
    class_name: str,
    invoke_method: str,
    candidates: list[str],
    is_async: bool,
) -> str:
    """Render wrapper for class-based agent."""
    options = ", ".join(candidates) if candidates else "(none detected)"
    return (
        "from __future__ import annotations\n\n"
        "# Auto-generated by Khaos.\n"
        "# Purpose: provide a deterministic Python handler boundary for class-based agents.\n\n"
        "import asyncio\n"
        "import importlib.util\n"
        "import inspect\n"
        "from pathlib import Path\n"
        "from typing import Any\n\n"
        "from khaos import khaos_agent\n"
        "from khaos.discovery.invoker import adapt_signature, extract_prompt\n\n"
        f"USER_SCRIPT = Path({str(user_script)!r}).expanduser().resolve()\n"
        f"CLASS_NAME = {class_name!r}  # candidates: {options}\n"
        f"INVOKE_METHOD = {invoke_method!r}\n\n"
        "_instance = None\n\n"
        "def _load() -> Any:\n"
        "    global _instance\n"
        "    if _instance is not None:\n"
        "        return _instance\n"
        "    spec = importlib.util.spec_from_file_location('khaos_user_agent', USER_SCRIPT)\n"
        "    if spec is None or spec.loader is None:\n"
        "        raise RuntimeError(f'Failed to import user script: {USER_SCRIPT}')\n"
        "    module = importlib.util.module_from_spec(spec)\n"
        "    spec.loader.exec_module(module)\n"
        "    cls = getattr(module, CLASS_NAME, None)\n"
        "    if cls is None:\n"
        "        raise RuntimeError(f\"Class '{CLASS_NAME}' not found in {USER_SCRIPT}\")\n"
        "    _instance = cls()\n"
        "    return _instance\n\n"
        "@khaos_agent\n"
        "def handle(message: dict) -> dict:\n"
        "    instance = _load()\n"
        "    method = getattr(instance, INVOKE_METHOD, None)\n"
        "    if not callable(method):\n"
        "        raise RuntimeError(\n"
        "            f\"Method '{INVOKE_METHOD}' not found on {CLASS_NAME}. \"\n"
        "            \"Edit INVOKE_METHOD to match your class's entrypoint.\"\n"
        "        )\n"
        "    args, kwargs = adapt_signature(method, message)\n"
        "    if kwargs:\n"
        "        result = method(**kwargs)\n"
        "    elif args:\n"
        "        result = method(*args)\n"
        "    else:\n"
        "        result = method()\n"
        "    if inspect.isawaitable(result):\n"
        "        result = asyncio.get_event_loop().run_until_complete(result)\n"
        "    if isinstance(result, dict):\n"
        "        return result\n"
        "    return {'value': result}\n"
    )
