"""Tool discovery for nanomon - finds all @nanomon decorated functions."""

from __future__ import annotations

import ast
import json
import logging
import os
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

logger = logging.getLogger(__name__)

# Default API URL (can be overridden via NANOMON_API_URL env var)
_DEFAULT_API_URL = "https://api-nano.nanomon.ai/api/v1"

# Directories to skip when scanning
SKIP_DIRS = {
    "venv",
    ".venv",
    "__pycache__",
    "node_modules",
    "dist",
    "build",
    ".git",
    ".tox",
    ".pytest_cache",
    ".mypy_cache",
    "egg-info",
}


def _should_skip(path: Path) -> bool:
    """Check if a path should be skipped during scanning."""
    parts = path.parts
    for part in parts:
        if part.startswith(".") or part in SKIP_DIRS or part.endswith(".egg-info"):
            return True
    return False


def _is_nanomon_decorator(decorator: ast.expr) -> bool:
    """Check if an AST node is a @nanomon decorator.

    Handles:
    - @nanomon
    - @nanomon()
    - @nanomon(name="...")
    - @context.nanomon()
    """
    # @nanomon (bare name)
    if isinstance(decorator, ast.Name):
        return decorator.id == "nanomon"

    # @nanomon() or @nanomon(name="...")
    if isinstance(decorator, ast.Call):
        func = decorator.func
        # @nanomon()
        if isinstance(func, ast.Name):
            return func.id == "nanomon"
        # @context.nanomon() or @something.nanomon()
        if isinstance(func, ast.Attribute):
            return func.attr == "nanomon"

    return False


def discover_tools(
    root_path: str | Path | None = None,
    print_results: bool = True,
) -> list[str]:
    """Scan codebase for @nanomon decorated functions.

    Walks through Python files in the project and finds all functions
    decorated with @nanomon, returning them in a deduplicated, sorted list.

    Args:
        root_path: Project root directory to scan. Defaults to current working directory.
        print_results: If True, prints discovered tools to console.

    Returns:
        Sorted list of "module.function_name" entries (no duplicates).

    Example:
        >>> tools = discover_tools()
        🔍 Discovered @nanomon tools:
          - tools.filesystem_tools.read_file
          - tools.filesystem_tools.write_file
          - tools.web_tools.search_web
    """
    root = Path(root_path) if root_path else Path(os.getcwd())
    discovered: set[str] = set()

    for py_file in root.rglob("*.py"):
        # Skip venv, __pycache__, etc.
        if _should_skip(py_file):
            continue

        try:
            source = py_file.read_text(encoding="utf-8", errors="ignore")
            tree = ast.parse(source)

            for node in ast.walk(tree):
                if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                    for decorator in node.decorator_list:
                        if _is_nanomon_decorator(decorator):
                            # Get relative path and convert to module notation
                            try:
                                rel_path = py_file.relative_to(root)
                            except ValueError:
                                rel_path = py_file

                            # Convert path to module name: tools/fs.py -> tools.fs
                            module_name = str(rel_path).replace(os.sep, ".").replace(".py", "")
                            entry = f"{module_name}.{node.name}"
                            discovered.add(entry)
                            break  # Found nanomon decorator, move to next function

        except (SyntaxError, UnicodeDecodeError, OSError):
            # Skip files that can't be parsed
            continue

    results = sorted(discovered)

    if print_results and results:
        print("🔍 Discovered @nanomon tools:")
        for tool in results:
            print(f"  - {tool}")
        print()

    return results


def sync_tools_to_api(
    tools: list[str],
    base_url: str | None = None,
    timeout_s: float = 5.0,
) -> bool:
    """Sync discovered tools to the nanomon API.

    Sends a PUT request to /api/v1/tools to replace all project tools
    with the provided list.

    Args:
        tools: List of tool names to sync (e.g., ["tools.fs.read_file", ...])
        base_url: API base URL. Defaults to NANOMON_API_URL env var or the default.
        timeout_s: Request timeout in seconds.

    Returns:
        True if sync was successful, False otherwise.

    Note:
        Requires NANOMON_CLIENT_ID and NANOMON_SECRET_ID environment variables
        to be set for authentication.
    """
    if not tools:
        logger.debug("No tools to sync")
        return True

    # Get API URL
    url = (base_url or os.environ.get("NANOMON_API_URL", _DEFAULT_API_URL)).rstrip("/")
    url = f"{url}/tools"

    # Get auth credentials
    client_id = os.environ.get("NANOMON_CLIENT_ID")
    project_secret = os.environ.get("NANOMON_SECRET_ID")

    if not client_id or not project_secret:
        logger.debug("Nanomon credentials not configured, skipping tool sync")
        return False

    # Prepare request
    payload = {"tools": tools}
    try:
        data = json.dumps(payload).encode("utf-8")
    except (TypeError, ValueError) as e:
        logger.warning(f"Failed to encode tools payload: {e}")
        return False

    headers = {
        "Content-Type": "application/json",
        "X-CLIENT-ID": client_id,
        "X-PROJECT-SECRET": project_secret,
    }

    try:
        request = Request(url, data=data, headers=headers, method="PUT")

        with urlopen(request, timeout=timeout_s) as response:
            status_code = response.getcode()
            if 200 <= status_code < 300:
                logger.debug(f"Successfully synced {len(tools)} tools to API")
                print(f"✓ Synced {len(tools)} tools to nanomon")
                return True

    except HTTPError as e:
        if e.code == 401:
            logger.warning("Nanomon authentication failed for tool sync")
        elif e.code == 403:
            logger.warning("Nanomon quota exceeded for tool sync")
        else:
            logger.warning(f"Nanomon API error ({e.code}) during tool sync")

    except URLError as e:
        logger.warning(f"Network error during tool sync: {e}")

    except TimeoutError:
        logger.warning("Timeout during tool sync")

    except Exception as e:
        logger.warning(f"Unexpected error during tool sync: {e}")

    return False


def discover_and_sync_tools(
    root_path: str | Path | None = None,
    print_results: bool = True,
    sync_to_api: bool = True,
) -> list[str]:
    """Discover @nanomon tools and optionally sync them to the API.

    Combines discover_tools() and sync_tools_to_api() for convenience.

    Args:
        root_path: Project root directory to scan.
        print_results: If True, prints discovered tools to console.
        sync_to_api: If True, syncs discovered tools to the nanomon API.

    Returns:
        Sorted list of discovered tool names.

    Example:
        >>> tools = discover_and_sync_tools()
        🔍 Discovered @nanomon tools:
          - tools.filesystem_tools.read_file
          - tools.filesystem_tools.write_file
        ✓ Synced 2 tools to nanomon
    """
    tools = discover_tools(root_path=root_path, print_results=print_results)

    if sync_to_api and tools:
        sync_tools_to_api(tools)

    return tools
