"""Unified search tool using ripgrep with grep fallback."""

import logging
import subprocess
from typing import Annotated, Any

from arcade_tdk import ToolContext, tool

from cade_mcp_local.config import (
    DEFAULT_IGNORE_PATTERNS,
    DEFAULT_TIMEOUT,
    get_project_root,
)
from cade_mcp_local.errors import (
    CommandTimeoutError,
    InvalidInputError,
    PathNotFoundError,
    SearchError,
)
from cade_mcp_local.utils import make_relative, resolve_path

log = logging.getLogger(__name__)


@tool(
    name="Search",
    desc=(
        "Search file contents for text or regex patterns. Uses ripgrep (fast, respects "
        ".gitignore) with automatic grep fallback. Supports file type filtering, glob "
        "patterns, context lines, and whole-word matching."
    ),
)
def search_tool(
    context: ToolContext,
    pattern: Annotated[str, "Text or regex pattern to search for."],
    path: Annotated[
        str,
        "File or directory to search (relative to project root).",
    ] = ".",
    file_type: Annotated[
        str,
        "File type filter (e.g., 'py', 'js', 'rust'). Maps to ripgrep --type.",
    ] = "",
    glob: Annotated[
        str,
        "Glob pattern to filter files (e.g., '*.py', '!*.test.js').",
    ] = "",
    file_extensions: Annotated[
        list[str],
        "File extensions to include (e.g., ['py', 'js']). Alternative to file_type.",
    ] = [],  # noqa: B006
    case_sensitive: Annotated[bool, "Whether search should be case sensitive."] = False,
    whole_word: Annotated[bool, "Match whole words only."] = False,
    hidden: Annotated[bool, "Search hidden files and directories."] = False,
    context_lines: Annotated[int, "Number of context lines before and after each match."] = 0,
    max_results: Annotated[int, "Maximum number of results to return."] = 100,
) -> Annotated[dict[str, Any], "Search results with file paths and matching lines."]:
    """Search file contents for patterns using ripgrep (with grep fallback)."""
    if not pattern or not pattern.strip():
        raise InvalidInputError("pattern", "cannot be empty")

    safe_path = resolve_path(path)
    if not safe_path.exists():
        raise PathNotFoundError(path)

    # Try ripgrep first, fall back to grep
    try:
        return _search_with_ripgrep(
            pattern,
            safe_path,
            path,
            file_type=file_type,
            glob=glob,
            file_extensions=file_extensions,
            case_sensitive=case_sensitive,
            whole_word=whole_word,
            hidden=hidden,
            context_lines=context_lines,
            max_results=max_results,
        )
    except FileNotFoundError:
        log.warning("ripgrep not found, falling back to grep")
        return _search_with_grep(
            pattern,
            safe_path,
            case_sensitive=case_sensitive,
            max_results=max_results,
        )


def _search_with_ripgrep(
    pattern: str,
    safe_path: Any,
    original_path: str,
    *,
    file_type: str,
    glob: str,
    file_extensions: list[str],
    case_sensitive: bool,
    whole_word: bool,
    hidden: bool,
    context_lines: int,
    max_results: int,
) -> dict[str, Any]:
    """Search using ripgrep with structured output."""
    cmd = ["rg", "--line-number"]
    if not case_sensitive:
        cmd.append("-i")
    if whole_word:
        cmd.append("-w")
    if hidden:
        cmd.append("--hidden")
    if context_lines > 0:
        cmd.extend(["-C", str(context_lines)])
    if file_type:
        cmd.extend(["-t", file_type])
    if glob:
        cmd.extend(["-g", glob])
    if file_extensions:
        for ext in file_extensions:
            cmd.extend(["-g", f"*.{ext.lstrip('.')}"])

    # Add ignore patterns
    for ignore in DEFAULT_IGNORE_PATTERNS:
        cmd.extend(["-g", f"!{ignore}"])

    # Note: rg -m limits matches per FILE, not total. We collect all results
    # and truncate in Python below.
    cmd.extend([pattern, str(safe_path)])

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=DEFAULT_TIMEOUT,
        )

        project_root = get_project_root()
        results = []
        files_matched: set[str] = set()

        for line in result.stdout.strip().split("\n"):
            if not line:
                continue
            if len(results) >= max_results:
                break
            parts = line.split(":", 2)
            if len(parts) >= 3:
                rel_path = make_relative(resolve_path(parts[0]), project_root)
                results.append(
                    {
                        "file": rel_path,
                        "line": int(parts[1]) if parts[1].isdigit() else 0,
                        "content": parts[2],
                    }
                )
                files_matched.add(rel_path)
            elif len(parts) == 2 and safe_path.is_file():
                results.append(
                    {
                        "file": original_path,
                        "line": int(parts[0]) if parts[0].isdigit() else 0,
                        "content": parts[1],
                    }
                )
                files_matched.add(original_path)

        return {
            "results": results,
            "total": len(results),
            "files_with_matches": len(files_matched),
            "exit_code": result.returncode,
        }

    except subprocess.TimeoutExpired:
        raise CommandTimeoutError(f"rg {pattern}", DEFAULT_TIMEOUT)
    except FileNotFoundError:
        raise  # Let caller handle fallback to grep
    except Exception as e:
        raise SearchError("ripgrep", str(e))


def _search_with_grep(
    pattern: str,
    directory: Any,
    *,
    case_sensitive: bool,
    max_results: int,
) -> dict[str, Any]:
    """Fallback search using grep."""
    cmd = ["grep", "-rn"]
    if not case_sensitive:
        cmd.append("-i")
    cmd.extend([pattern, str(directory)])

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=DEFAULT_TIMEOUT,
        )

        project_root = get_project_root()
        results = []
        files_matched: set[str] = set()

        for line in result.stdout.strip().split("\n")[:max_results]:
            if not line:
                continue
            parts = line.split(":", 2)
            if len(parts) >= 3:
                rel_path = make_relative(resolve_path(parts[0]), project_root)
                results.append(
                    {
                        "file": rel_path,
                        "line": int(parts[1]) if parts[1].isdigit() else 0,
                        "content": parts[2].strip(),
                    }
                )
                files_matched.add(rel_path)

        return {
            "results": results,
            "total": len(results),
            "files_with_matches": len(files_matched),
            "exit_code": result.returncode,
        }
    except Exception as e:
        return {
            "results": [],
            "total": 0,
            "files_with_matches": 0,
            "error": str(e),
        }
